Easy | 剑指 Offer 53 - II. 0~n-1中缺失的数字 | 二分法
剑指 Offer 53 - II. 0~n-1中缺失的数字
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
示例 2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8
解题思路
从题目里很容易知道, 比缺失的数字小的元素, 其元素值和下标是对应的, 比缺失值数字大的元素, 其元素值一定比下标大, 而且是大1。
所以这里可以用二分法的方法, 找到第一个下标和其元素值不相等的元素, (每次排除掉下标和元素值相等的左边一半, 或者排除掉下标和元素不相等的右边一半(保留当前不等值))来缩小查找的范围。
这道题的二分法的代码如下:
public int missingNumber(int[] nums) {
int low = 0, upper = nums.length - 1;
if (nums[nums.length-1] == nums.length - 1) {
return nums.length;
}
while(low < upper) {
int mid = (low + upper) / 2;
if (nums[mid] > mid) {
upper = mid;
} else {
low = mid + 1;
}
}
return low;
}
下面是一些二分法的细节, 需要注意
一般二分查找的时候, 我们的思路是每次排除一半, 最终剩下的那一个数就是我们要找的目标值。所以循环成立的条件设置为low < upper
, 最终剩下一个数的时候会退出循环。
如果代码写成下面的样子
public int missingNumber(int[] nums) {
int low = 0, upper = nums.length - 1;
while(low < upper) {
int mid = (low + upper) / 2;
if (nums[mid] > mid) {
upper = mid;
} else {
low = mid + 1;
}
}
return low;
}
是有问题的, 问题在哪?
我们上面的代码的策略是, 对于下标和元素值相等的左边一半, 全部排除(不保留当前值), 对于不相等的排除右边但是保留当前值。按照这样的一个设想, 最终剩下的哪个值就是我们要找的第一个不相等的元素, 返回其下标就可以。但是有种情况是上面的代码的策略执行不了的。比如说数组是[0, 1, 2, 3, 4], 这里面没有那个我们在二分过程所要保留的那个下标与元素值不等的当前值, 所以最终剩下一个数组是4, 他和答案并不相符, 所以这个时候, 我们需要进行特判。需要判断当前数组里, 是否存在着这个下标与元素值不等的数字, 如果不存在, 可以直接反馈结果。也就是需要加上if (nums[nums.length-1] == nums.length - 1) return nums.length
这一行。
上面是对在while循环条件为low < upper
的造成的问题的其中一种解法。既然问题是这个while循环造成的, 那么我直接让low <= upper
不就可以了吗?既然改成这样的, 那么在每次二分的过程, 就不能保留当前值了, 二分法必须写成下面的方式, 在二分排序一半的时候就不能保留当前值, 这样在二分结束时, upper指向目标值前一个元素, low指向目标值。因为当二分到只有一个元素时, 如果这个元素下标与其值不等, 那么upper-1, 并且low就是目标元素下标。如果这个元素下标与其值相等, 那么low+1, 并且upper是最右边一个下标与其值相等的元素, low+1就是第一个下标与其值不等的元素。
public int missingNumber(int[] nums) {
int low = 0, upper = nums.length - 1;
while(low <= upper) {
int mid = (low + upper) / 2;
if (nums[mid] > mid) {
upper = mid - 1;
} else {
low = mid + 1;
}
}
return low;
}