文章目錄
- 題目
- 標題和出處
- 難度
- 題目描述
- 要求
- 示例
- 數據范圍
- 進階
- 前言
- 解法一
- 思路和算法
- 代碼
- 復雜度分析
- 解法二
- 思路和算法
- 代碼
- 復雜度分析
- 解法三
- 思路和算法
- 代碼
- 復雜度分析
題目
標題和出處
標題:尋找重復數
出處:287. 尋找重復數
難度
6 級
題目描述
要求
給定一個包含 n + 1 \texttt{n} + \texttt{1} n+1 個整數的數組 nums \texttt{nums} nums,每個整數都在范圍 [1, n] \texttt{[1, n]} [1,?n] 內(包括 1 \texttt{1} 1 和 n \texttt{n} n)。
數組 nums \texttt{nums} nums 只有一個重復的整數,返回這個重復的數。
要求不修改數組 nums \texttt{nums} nums 且只用常量級的額外空間。
示例
示例 1:
輸入: nums = [1,3,4,2,2] \texttt{nums = [1,3,4,2,2]} nums?=?[1,3,4,2,2]
輸出: 2 \texttt{2} 2
示例 2:
輸入: nums = [3,1,3,4,2] \texttt{nums = [3,1,3,4,2]} nums?=?[3,1,3,4,2]
輸出: 3 \texttt{3} 3
示例 3:
輸入: nums = [3,3,3,3,3] \texttt{nums = [3,3,3,3,3]} nums?=?[3,3,3,3,3]
輸出: 3 \texttt{3} 3
數據范圍
- 1 ≤ n ≤ 10 5 \texttt{1} \le \texttt{n} \le \texttt{10}^\texttt{5} 1≤n≤105
- nums.length = n + 1 \texttt{nums.length} = \texttt{n} + \texttt{1} nums.length=n+1
- 1 ≤ nums[i] ≤ n \texttt{1} \le \texttt{nums[i]} \le \texttt{n} 1≤nums[i]≤n
- nums \texttt{nums} nums 中的所有整數都只出現一次,除了一個整數出現兩次或多次
進階
- 如何證明 nums \texttt{nums} nums 中至少存在一個重復的數字?
- 你可以設計一個線性級時間復雜度的解決方案嗎?
前言
由于數組的長度是 n + 1 n + 1 n+1,且最多包含 n n n 個不同的整數。根據抽屜原理(或鴿籠原理)可知,將數組的 n + 1 n + 1 n+1 個位置分配到 n n n 個不同的整數,至少有兩個位置分配到同一個整數,即該整數在數組中重復。因此數組中至少存在一個重復的數字。
由于這道題要求不修改數組且空間復雜度是 O ( 1 ) O(1) O(1),因此排序、哈希表等解法都是不允許的,需要使用其他解法尋找重復的數字。
解法一
思路和算法
每個正整數的二進制表示中至少有一位是 1 1 1。對于二進制表示的每一位,分別考慮數組 nums \textit{nums} nums 中的所有整數在該位的 1 1 1 的次數 countArr \textit{countArr} countArr 和范圍 [ 1 , n ] [1, n] [1,n] 中的所有整數在該位的 1 1 1 的次數 countNum \textit{countNum} countNum。假設整數 x x x 在數組 nums \textit{nums} nums 中出現超過一次,則 x x x 的二進制表示的每個 1 1 1 所在的位都滿足 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum。理由如下。
-
如果 x x x 出現兩次,則范圍 [ 1 , n ] [1, n] [1,n] 中的所有整數都在數組 nums \textit{nums} nums 中出現,因此對于 x x x 中等于 1 1 1 的每一位都有 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum。
-
如果 x x x 出現超過兩次,則范圍 [ 1 , n ] [1, n] [1,n] 中的部分整數不在數組 nums \textit{nums} nums 中出現,可以看成 x x x 替代了這部分整數。對于 x x x 中等于 1 1 1 的每一位,被替代的整數在相應位的值等于 0 0 0 或 1 1 1。替代之前有 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum,替代之后同樣有 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum。
除了 x x x 的二進制表示的每個 1 1 1 所在的位以外,其余位都不滿足 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum。因此上述結論的逆命題也成立:假設所有滿足 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum 的位組成的整數是 x x x,則 x x x 在數組 nums \textit{nums} nums 中出現超過一次。
根據上述分析,可以使用位運算尋找重復數。首先找到 n n n 的二進制表示的最高有效位,即最高位 1 1 1 所在的位數,然后依次遍歷最低有效位到最高有效位,對于每一位計算 countArr \textit{countArr} countArr 和 countNum \textit{countNum} countNum,如果 countArr > countNum \textit{countArr} > \textit{countNum} countArr>countNum 則將該位加到重復數中。遍歷結束之后即可得到重復數。
代碼
class Solution {public int findDuplicate(int[] nums) {int n = nums.length - 1;int highBit = 0;int temp = n;while (temp != 0) {highBit = temp & (-temp);temp -= highBit;}int duplicate = 0;for (int i = 1; i <= highBit; i <<= 1) {int countArr = 0, countNum = 0;for (int num : nums) {int bit = num & i;if (bit != 0) {countArr++;}}for (int j = 1; j <= n; j++) {int bit = j & i;if (bit != 0) {countNum++;}}if (countArr > countNum) {duplicate += i;}}return duplicate;}
}
復雜度分析
-
時間復雜度: O ( n log ? n ) O(n \log n) O(nlogn),其中 n n n 是數組 nums \textit{nums} nums 的長度減 1 1 1。需要遍歷的二進制表示位數是 O ( log ? n ) O(\log n) O(logn),對于每一位都需要 O ( n ) O(n) O(n) 的時間計算該位在重復數中是否等于 1 1 1,時間復雜度是 O ( n log ? n ) O(n \log n) O(nlogn)。
-
空間復雜度: O ( 1 ) O(1) O(1)。
解法二
思路和算法
假設有一個長度為 n + 1 n + 1 n+1 的數組 counts \textit{counts} counts,其中 counts [ i ] \textit{counts}[i] counts[i] 表示數組 nums \textit{nums} nums 中的不超過 i i i 的整數個數。以下用 x x x 表示重復數。
如果 x x x 出現兩次,則當 i < x i < x i<x 時 counts [ i ] = i \textit{counts}[i] = i counts[i]=i,當 i ≥ x i \ge x i≥x 時 counts [ i ] = i + 1 > i \textit{counts}[i] = i + 1 > i counts[i]=i+1>i。
如果 x x x 出現超過兩次,則范圍 [ 1 , n ] [1, n] [1,n] 中的部分整數不在數組 nums \textit{nums} nums 中出現,可以看成 x x x 替代了這部分整數,假設只有一個整數被替代,用 j j j 表示被替代的整數,分別考慮 j < x j < x j<x 和 j > x j > x j>x 的情況。
-
如果 j < x j < x j<x,則對于 i < x i < x i<x,不超過 i i i 的整數個數不變或減少,因此 counts [ i ] ≤ i \textit{counts}[i] \le i counts[i]≤i,對于 i ≥ x i \ge x i≥x,不超過 i i i 的整數個數不變,因此 counts [ i ] > i \textit{counts}[i] > i counts[i]>i。
-
如果 j > x j > x j>x,則對于 i < x i < x i<x,不超過 i i i 的整數個數不變,因此 counts [ i ] = i \textit{counts}[i] = i counts[i]=i,對于 i ≥ x i \ge x i≥x,不超過 i i i 的整數個數不變或增加,因此 counts [ i ] > i \textit{counts}[i] > i counts[i]>i。
因此當 i < x i < x i<x 時 counts [ i ] ≤ i \textit{counts}[i] \le i counts[i]≤i,當 i ≥ x i \ge x i≥x 時 counts [ i ] > i \textit{counts}[i] > i counts[i]>i。如果被替代的整數有多個,該結論仍成立。
重復數 x x x 是使得 counts [ x ] > x \textit{counts}[x] > x counts[x]>x 成立的最小整數 x x x,可以使用二分查找的方法尋找重復數。
用 low \textit{low} low 和 high \textit{high} high 分別表示二分查找的下界和上界。由于 x x x 在范圍 [ 1 , n ] [1, n] [1,n] 中,因此初始時 low = 1 \textit{low} = 1 low=1, high = n \textit{high} = n high=n。
每次查找時,取 mid \textit{mid} mid 為 low \textit{low} low 和 high \textit{high} high 的平均數向下取整,計算數組 nums \textit{nums} nums 中的不超過 mid \textit{mid} mid 的整數個數 counts [ mid ] \textit{counts}[\textit{mid}] counts[mid],執行如下操作。
-
如果 counts [ mid ] ≤ mid \textit{counts}[\textit{mid}] \le \textit{mid} counts[mid]≤mid,則重復數 x x x 小于等于 mid \textit{mid} mid,因此在 [ low , mid ] [\textit{low}, \textit{mid}] [low,mid] 中繼續查找。
-
如果 counts [ mid ] > mid \textit{counts}[\textit{mid}] > \textit{mid} counts[mid]>mid,則重復數 x x x 大于 mid \textit{mid} mid,因此在 [ mid + 1 , high ] [\textit{mid} + 1, \textit{high}] [mid+1,high] 中繼續查找。
當 low = high \textit{low} = \textit{high} low=high 時,查找結束,此時 low \textit{low} low 即為重復數 x x x。
實現方面,并不需要顯性創建數組 counts \textit{counts} counts,而是可以在二分查找的過程中根據當前的 mid \textit{mid} mid 遍歷數組 nums \textit{nums} nums 計算不超過 mid \textit{mid} mid 的整數個數,因此空間復雜度是 O ( 1 ) O(1) O(1)。
代碼
class Solution {public int findDuplicate(int[] nums) {int n = nums.length - 1;int low = 1, high = n;while (low < high) {int mid = low + (high - low) / 2;int count = 0;for (int num : nums) {if (num <= mid) {count++;}}if (count > mid) {high = mid;} else {low = mid + 1;}}return low;}
}
復雜度分析
-
時間復雜度: O ( n log ? n ) O(n \log n) O(nlogn),其中 n n n 是數組 nums \textit{nums} nums 的長度減 1 1 1。需要執行 O ( log ? n ) O(\log n) O(logn) 次二分查找,每次二分查找需要 O ( n ) O(n) O(n) 的時間遍歷數組 nums \textit{nums} nums 計算不超過特定值的整數個數,時間復雜度是 O ( n log ? n ) O(n \log n) O(nlogn)。
-
空間復雜度: O ( 1 ) O(1) O(1)。
解法三
思路和算法
將數組看成有向圖,范圍 [ 0 , n ] [0, n] [0,n] 中的每個整數是一個結點,對于 0 ≤ i ≤ n 0 \le i \le n 0≤i≤n 的每個下標 i i i,存在一條從 i i i 指向 nums [ i ] \textit{nums}[i] nums[i] 的有向邊。對于重復數 x x x,存在至少兩條指向 x x x 的邊,因此有向圖中存在環。
這道題可以看成在有向圖中尋找環的入口,可以使用「環形鏈表 II」的快慢指針做法。以下只說明使用快慢指針尋找重復數的做法,正確性證明見「環形鏈表 II 的題解」。
尋找重復數分成兩步。
第一步是使用快慢指針遍歷有向圖尋找相遇點。
初始時,快指針和慢指針都位于整數 0 0 0。每次將快指針移動兩步,慢指針移動一步,在至少移動一次的情況下,當快指針和慢指針相遇時,相遇的位置為相遇點。
第二步是將兩個指針分別從起點和相遇點開始遍歷有向圖尋找重復數。
初始時,兩個指針分別位于整數 0 0 0 和相遇點。每次將兩個指針各移動一步,兩個指針相遇的整數即為重復數。
代碼
class Solution {public int findDuplicate(int[] nums) {int fast = 0, slow = 0;int meet = -1;while (meet < 0) {fast = nums[nums[fast]];slow = nums[slow];if (fast == slow) {meet = fast;}}int pointer1 = 0, pointer2 = meet;while (pointer1 != pointer2) {pointer1 = nums[pointer1];pointer2 = nums[pointer2];}return pointer1;}
}
復雜度分析
-
時間復雜度: O ( n ) O(n) O(n),其中 n n n 是數組 nums \textit{nums} nums 的長度減 1 1 1。快慢指針的時間復雜度是 O ( n ) O(n) O(n)。
-
空間復雜度: O ( 1 ) O(1) O(1)。