题目描述
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。
找到所有在 [1, n] 范围之间没有出现在数组中的数字。
您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
示例:
输入:
[4,3,2,7,8,2,3,1]
输出:[5,6]
题解
注意题目要求不能使用额外空间,这就是题目的难点所在。
这道题的描述部分包含了一个非常重要的信息,1 ≤ a[i] ≤ n
,即每个数字本身都对应一个i-1
的数组下标。我们可以利用数组内容本身跟数字下标的关联找出缺失的数字。
扫描两遍数组:
- 第一遍,将所有数字做标记
- 第二遍,根据标记信息找出缺失的数字。
下面来看详细分析
假设有数组[1,2,3,4,5,6]
这个数组是有序的,而且也没有缺失数字,范围是[1,6]
仔细看,数组中的每个元素,其实和数组下标是有一一对应关系的
这里的对应关系就是:
- 数组值1对应下标0
- 数组值2对应下标1
- 数组值3对应下标2
- 数组值4对应下标3
- 数组值5对应下标4
- 数组值6对应下标5
也就是数组下标+1
正好等于 数组中的值
如果是一个乱序的数组会怎样呢?
假设数组是[5,4,6,3,1,2]
,范围是[1,6]
,也没有缺失数字
这里仍然有一一对应关系:
- 数组值5对应下标4
- 数组值4对应下标3
- 数组值6对应下标5
- 数组值3对应下标2
- 数组值1对应下标0
- 数组值2对应下标1
没有缺失数字的情况下,不管是有序的、还是乱序的,都跟下标有一一对应关系。
现在我们来分析一个缺失数字的例子
假设有数组[1,2,3,4,6,6]
缺少数字5
我们用值和下标对应的这么一层关系,将数组重写一遍
- 第一个值是1,对应下标是0,将arr[0]设置为-arr[0],即-1
- 第二个值是2,对应下标是1,将arr[1]设置为-arr[1],即-2
- 第三个值是3,对应下标是2,将arr[2]设置为-arr[2],即-3
- 第四个值是4,对应下标是3,将arr[3]设置为-arr[3],即-4
- 第五个值是6,对应下标是5,
将arr[5]设置为-arr[5],即-6
- 第六个值是6,对应下标是5,
将arr[5]设置为-arr[5],即-6
第五个、第六个值相同,他们修改的是同一个下标,都将arr[5]改了一次
但是arr[4]这个位置没动过
重写了一遍数组之后,数组就变成了这个样子:
由于下标4
应该对应数字5
,现在缺少了这个值,所以没人设置这个位置,于是第一遍处理完后,只有下标4
这个位置的值是正数,其他位置的全部都是负数。
这就好办了,我们遍历一遍数组,找到大于0的数,这个数是6
,对应下标是4
,所以缺失的数字是5
最后再看一个更复杂的例子
数组[4,3,2,7,8,2,3,1]
,缺少5,6两个数字
我们来看下第一趟的处理过程:
- 第一个数字是4,对应下标3,将arr[3]设置为-7
- 第二个数字是3,对应下标2,将arr[2]设置为-2
- 第三个数字是2,对应下标1,将arr[1]设置为-3
- 第四个数字是7,对应下标6,将arr[6]设置为-3
- 第五个数字是8,对应下标7,将arr[7]设置为-1
- 第六个数字是2,对应下标1,将arr[1]设置为-3
- 第七个数字是3,对应下标2,将arr[2]设置为-2
- 第八个数字是1,对应下标0,将arr[0]设置为-4
第一趟处理完了之后,我们开始第二趟扫描,也就是上图中第二个数组
这个数组中8,2两个元素是大于0的
8对应下标4,所以4+1,即缺少5
这个数字
2对应下标5,所以5+1,即缺少6
这个数字
时间复杂度:O(N)
空间复杂度:O(1)
java代码:
class Solution {
public List findDisappearedNumbers(int[] nums) {
List res = new ArrayList();//第一遍扫描,根据数组的值找到对应的下标,比如3对应下标2//将arr[2]设置成负数for(int i=0;i int index = Math.abs(nums[i])-1;if(nums[index]>0) {
nums[index] *= -1;
}
}//第二遍扫描,找到所有非负数,非负数所在的下标+1,即为缺失的数字for(int i=1;i<=nums.length;++i) {if(nums[i-1]>0) {
res.add(i);
}
}return res;
}
}
python代码:
class Solution(object):
def findDisappearedNumbers(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
res = []
# 第一遍扫描,根据数组的值找到对应的下标,比如3对应下标2
# 将arr[2]设置成负数
for i in nums:
index = abs(i)-1
if nums[index]>0:
nums[index] *= -1
# 第二遍扫描,找到所有非负数,非负数所在的下标+1,即为缺失的数字
for i in xrange(len(nums)):
if nums[i]>0:
res.append(i+1)
return res
推荐阅读 点击标题可跳转删除排序数组中的重复项
移动零
盛水最多的容器
看完本文有收获?请转发分享给更多人
好文章,我在看❤️