来源:LeetCode
难度:简单
问题详情:
一个长度为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
在真正开始介绍各种算法前,先以表格形式展示各自的时间复杂度和空间复杂度,其中
n
n
n表示数组nums
的长度。
算法 | 时间复杂度 | 空间复杂度 |
---|---|---|
暴力查询 | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) |
二分查找 | O( l o g ( n ) log(n) log(n)) | O ( 1 ) O(1) O(1) |
暴力查询的思想是遍历数组的每一个元素,并判断当前元素值是否与当前下标值是否相等,如果不相等,就说明当前下标值缺失,返回即可。
举例来说:
nums=[0,1,3]
在遍历到nums[2]
时发现其并不是等于2
,所以可以断定2
是缺失的数据、
特例说明,对于nums=[0,1,2]
这样看着并不缺失数据的数组,因为题目中明确说明缺失了一位,那么只能是3
是缺失的数值。
def missingNumber(self, nums: List[int]) -> int:
for i, item in enumerate(nums):
if i != item:
return i
return i+1
时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)
题目中说了nums
是个有序数组,对于有序数组的搜索问题,那么自然就能想到使用二分查找的方法。
一开始本人想到的是利用左右指针指向的数据之和与“两位中位数”之和的大小来判断到底是哪个区间缺失了。
举例来说:
nums=[0,1,3,4]
但是这种想法实际实现出来,需要考虑的特殊情况太多,暂时不做过多介绍,贴出代码:
def missingNumber(self, nums: List[int]) -> int:
length = len(nums)
# 特殊处理[1,2]这类缺失0的数据
if nums[0] != 0:
return 0
if length == 1:
return length
left, right = 0, length - 1
missing = 'left' # 表示中位数的左边还是右边缺失
while left < right:
t1 = nums[left] + nums[right]
length = right - left + 1
a = left + length // 2
b = left + (length - 1) // 2
t2 = nums[a] + nums[b]
if t1 == t2:
if nums[a] - nums[b] == 2:
return nums[a] - 1
if missing == 'right':
return nums[left] - 1
else:
return nums[right] + 1
# 比如0 1 2 3 4 5 6 7 中位数是3 和 4,如果缺失 1的话,那就是0 2 3 4 5 6 7,t1 = 7, t2 = 8
# 也就是缺失中位数左边的数值,t1 < t2
elif t1 < t2:
missing = 'left'
right = b
else:
missing = 'right'
left = a
但是有另一种基于二分查找思想的解法。
举例来说:
nums=[0,1,3,4]
代码如下:
def missingNumber(nums: List[int]) -> int:
left, right = 0, len(nums) - 1
# 这里取=是为了判断left=right时,当前元素的数值是否等于索引
while left <= right:
mid = (left + right) // 2
if nums[mid] == mid:
left = mid + 1
else:
right = mid - 1
return left
时间复杂度为 O ( l o g ( n ) ) O(log(n)) O(log(n)),空间复杂度为 O ( 1 ) O(1) O(1)