题目描述:
给你一个含 n
个整数的数组 nums
,其中 nums[i]
在区间 [1, n]
内。请你找出所有在 [1, n]
范围内但没有出现在 nums
中的数字,并以数组的形式返回结果。
进阶:你能在不使用额外空间且时间复杂度为 O(n)
的情况下解决这个问题吗? 你可以假定返回的数组不算在额外空间内。
示例 1:
1 | 输入:nums = [4,3,2,7,8,2,3,1] |
示例 2:
1 | 输入:nums = [1,1] |
提示:
- $n == nums.length$
- $1 <= n <= 10^5$
- $1 <= nums[i] <= n$
链接:
https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array
题目分析
我们需要找到数组中没有出现过的数字,最容易想到的就是使用哈希表记录出现过的数,这样需要空间大小为 $O(n)$ 的哈希表。而进阶要求中说不使用额外空间,那我们就需要思考原数组如何利用。可以知道原数组的大小也是 n
,是否可以将原数组当成一个哈希表来使用呢?区间 [1, n]
的各个数字刚好对应数组 [0, n-1]
各个下标。
遍历数组,对于出现的元素 num
我们就标记 nums[num-1]
,我们考虑如何进行标记,因为 nums[num-1]
可能还未被遍历到,需要标记后仍然能使用。一种方法是标记的时候将其作为下一个查找的目标,那样需要在数组中跳跃。另一种就是考虑标记后能够还原成原数字的方法,比如数字的范围是 1~n,那我们可以将其置为相反数,还原的时候取绝对值就可以。
具体操作就是每次将需要标记的下标计算出来(当前元素的绝对值减一),然后判断其是否未标记(仍然是正数),然后进行标记(置为相反数)。全部标记完毕后,我们就可以遍历数组,对于每个仍是正数的元素,说明其下标加一的那个数不存在数组中,加入结果数组。对于负数的元素我们进行还原(这个操作好像也可以不要,题目不检查是否已经改动了原数组)。这样就达到了进阶的要求。
PS:这样的算法也是一种原地算法,就是在原数组进行操作的算法。官方题解也是差不多的思路,而进行标记的方法是加上 n,这样也会超过原数字的范围,然后还原的方法是对 n 取余。这样也是一种方法,好处是操作之前不需要先判断是否已经标记,因为再次加 n 也不会造成影响。但是不好的地方在于其实这样是会越界的,题目中 n 的范围可以到 $10^5$,这样最大的标记可以到达 $10^{10}$,这是超过了 int 的范围的,貌似最后的测例并没有这么大,所以官方题解可以通过所有测例。
1 | class Solution { |
时间复杂度:$O(n)$,其中 $n$ 是数组的大小。我们对数组进行了两次遍历,一次是进行标记,一次是寻找结果。
空间复杂度:$O(1)$。我们在原数组进行操作,而结果数组不计入空间复杂度。