1:问题描述
来源: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
2:问题分析
2.1 时间复杂度和空间复杂度
在真正开始介绍各种算法前,先以表格形式展示各自的时间复杂度和空间复杂度,其中
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) |
2.2 暴力查询
暴力查询的思想是遍历数组的每一个元素,并判断当前元素值是否与当前下标值是否相等,如果不相等,就说明当前下标值缺失,返回即可。
举例来说:
nums=[0,1,3]
在遍历到nums[2]
时发现其并不是等于2
,所以可以断定2
是缺失的数据、
特例说明,对于nums=[0,1,2]
这样看着并不缺失数据的数组,因为题目中明确说明缺失了一位,那么只能是3
是缺失的数值。
2.2.1 代码
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)
2.3 二分查找
题目中说了nums
是个有序数组,对于有序数组的搜索问题,那么自然就能想到使用二分查找的方法。
2.3.1 二分查找1
一开始本人想到的是利用左右指针指向的数据之和与“两位中位数”之和的大小来判断到底是哪个区间缺失了。
举例来说:
nums=[0,1,3,4]
- l e f t = 0 , r i g h t = 3 , m i d 1 = ( l e f t + r i g h t ) / / 2 = 1 , m i d 2 = ( l e f t + r i g h t − 1 ) / / 2 = 1 left = 0, right=3, mid1 = (left + right)//2=1, mid2 = (left+right-1)//2=1 left=0,right=3,mid1=(left+right)//2=1,mid2=(left+right−1)//2=1
- n u m s [ 0 ] + n u m s [ 3 ] = 4 , n u m s [ 1 ] + n u m s [ 1 ] = 3 , 4 < 3 nums[0]+nums[3]=4,nums[1]+nums[1]=3,4<3 nums[0]+nums[3]=4,nums[1]+nums[1]=3,4<3,而且可以看出是中位数的右区间缺失,说明右边的数据缺失, n u m s [ l e f t ] + n u m s [ r i g h t ] < n u m s [ m i d 1 ] + n u m s [ m i d 2 ] nums[left]+nums[right]<nums[mid1]+nums[mid2] nums[left]+nums[right]<nums[mid1]+nums[mid2]
但是这种想法实际实现出来,需要考虑的特殊情况太多,暂时不做过多介绍,贴出代码:
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
2.3.1 二分查找2
但是有另一种基于二分查找思想的解法。
举例来说:
nums=[0,1,3,4]
- l e f t = 0 , r i g h t = 3 left =0,right=3 left=0,right=3
- m i d = ( l e f t + r i g h t ) / / 2 = 1 mid = (left + right)//2=1 mid=(left+right)//2=1
- 如果 n u m s [ m i d ] = m i d nums[mid] =mid nums[mid]=mid,那么说明 [ l e f t , m i d ] [left,mid] [left,mid]区间内的数据是没有缺失的(这里可以看出来是与暴力查询的思想是相同的), l e f t = m i d + 1 left=mid+1 left=mid+1
- 如果不相等,那么说明 [ l e f t , m i d ] [left,mid] [left,mid]区间内的数据是缺失的, r i g h t = m i d − 1 right=mid-1 right=mid−1
代码如下:
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)