Find the Missing Number
Question
Problem Statement
Given an array contains N numbers of 0 .. N, find which number doesn't exist in the array.
Example
Given N = 3
and the array [0, 1, 3]
, return 2
.
Challenge
Do it in-place with extra memory and time.
題解1 - 位運算
和找單數的題類似,這裡我們不妨試試位運算中 Exlcusive Or (XOR) 的思路。最開始自己想到的是利用相鄰項異或結果看是否會有驚喜,然而發現 a^(a+1) != a^a + a^1
之後眼淚掉下來... 如果按照找單數的做法,首先對陣列所有元素異或,得到數x1
, 現在的問題是如何利用x1
得到缺失的數,由於找單數中其他數都是成對出現的,故最後的結果即是單數,這裏每個數都是單數,怎麼辦呢?我們現在再來分析下如果沒有缺失數的話會是怎樣呢?假設所有元素異或得到數x2
, 數x1
和x2
有什麼差異呢?假設缺失的數是x0
,那麼容易知道x2 = x1 ^ x0
, 相當於現在已知x1
和x2
,要求x0
. 根據 Bit Manipulation 中總結的交換律,x0 = x1 ^ x2
.
位運算的題往往比較靈活,需要好好利用常用等式變換。
Java
public class Solution {
/**
* @param nums: an array of integers
* @return: an integer
*/
public int findMissing(int[] nums) {
if (nums == null || nums.length == 0) return -1;
// get xor from 0 to N excluding missing number
int x1 = 0;
for (int i : nums) {
x1 ^= i;
}
// get xor from 0 to N
int x2 = 0;
for (int i = 0; i <= nums.length; i++) {
x2 ^= i;
}
// missing = x1 ^ x2;
return x1 ^ x2;
}
}
源碼分析
略
複雜度分析
遍歷原陣列和 N+1大小的陣列,時間複雜度 , 空間複雜度 .
題解2 - 桶排序
非常簡單直觀的想法——排序後檢查缺失元素,但是此題中要求時間複雜度爲 , 因此如果一定要用排序來做,那一定是使用非比較排序如桶排序或者計數排序。題中另一提示則是要求只使用 的額外空間,那麼這就是在提示我們應該使用原地交換。根據題意,元素應無重複,可考慮使用桶排,索引和值一一對應即可。第一重 for 循環遍歷原陣列,內循環使用 while, 調整索引處對應的值,直至相等或者索引越界爲止,for 循環結束時桶排結束。最後再遍歷一次陣列找出缺失元素。
初次接觸這種題還是比較難想到使用桶排這種思想的,尤其是利用索引和值一一對應這一特性找出缺失元素,另外此題在實際實現時不容易做到 bug-free, while 循環處容易出現死循環。
Java
public class Solution {
/**
* @param nums: an array of integers
* @return: an integer
*/
public int findMissing(int[] nums) {
if (nums == null || nums.length == 0) return -1;
bucketSort(nums);
// find missing number
for (int i = 0; i < nums.length; i++) {
if (nums[i] != i) {
return i;
}
}
return nums.length;
}
private void bucketSort(int[] nums) {
for (int i = 0; i < nums.length; i++) {
while (nums[i] != i) {
// ignore nums[i] == nums.length
if (nums[i] == nums.length) {
break;
}
int nextNum = nums[nums[i]];
nums[nums[i]] = nums[i];
nums[i] = nextNum;
}
}
}
}
源碼分析
難點一在於正確實現桶排,難點二在於陣列元素中最大值 N 如何處理。N 有三種可能:
- N 不在原陣列中,故最後應該返回 N
- N 在原陣列中,但不在陣列中的最後一個元素
- N 在原陣列中且在陣列最後一個元素
其中情況1在遍歷桶排後的陣列時無返回,最後返回 N.
其中2和3在 while 循環處均會遇到 break 跳出,即當前這個索引所對應的值要麼最後還是 N,要麼就是和索引相同的值。如果最後還是 N, 也就意味着原陣列中缺失的是其他值,如果最後被覆蓋掉,那麼桶排後的陣列不會出現 N, 且缺失的一定是 N 之前的數。
綜上,這裏的實現無論 N 出現在哪個索引都能正確返回缺失值。實現上還是比較巧妙的,所以說在沒做過這類題時要在短時間內 bug-free 比較難,當然也可能是我比較菜...
另外一個難點在於如何保證或者證明 while 一定不會出現死循環,可以這麼理解,如果 while 條件不成立且未出現nums.length
這個元素,那麼就一定會使得一個元素正確入桶,又因爲沒有重復元素出現,故一定不會出現死循環。
複雜度分析
桶排時間複雜度 , 空間複雜度 . 遍歷原陣列找缺失數時間複雜度 . 故總的時間複雜度爲 , 空間複雜度 .