448. *Find All Numbers Disappeared in an Array
https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/description/
题目描述
Given an array of integers where 1 ≤ a[i] ≤ n
(n = size of array
), some elements appear twice and others appear once.
Find all the elements of [1, n]
inclusive that do not appear in this array.
Could you do it without extra space and in O ( n ) O(n) O(n) runtime? You may assume the returned list does not count as extra space.
Example:
Input:
[4,3,2,7,8,2,3,1]
Output:
[5,6]
解题思路
思路: 看到类似这种数组中的元素的值限定在 1 ~ N
之间的, 可能就可以考虑使用将元素值作为索引的方法… 由于每个元素值的大小都是 1 <= a[i] <= N
, 那么访问 a[a[i] - 1]
是没有问题的. 考虑上面这个数组:
# 假设数组的索引从 1 开始
index : 1 2 3 4 5 6 7 8
value : 4 3 2 7 8 2 3 1
现在从索引 1
开始, 如果依次将 a[abs(a[i])]
的值改为负数, 但如果 a[abs(a[i])]
已经是负数了, 那么就不用修改了.
# 假设数组的索引从 1 开始
index : 1 2 3 4 5 6 7 8
value : -4 -3 -2 -7 8 2 -3 -1
可以看到只有 {5, 6}
处的元素依然是正数, 那么消失的数字就是 {5, 6}
. 由于下面的代码中索引从 0
开始, 所以最后要加 1
.
核心思路就是: 对于 nums
中的每个元素, 将 nums[i] - 1
对应的那个元素设置为负值, 表示那个元素已经被访问过.
C++ 实现 1
通过将 nums[std::abs(nums[i]) - 1] > 0
的数值设置为负, 从而得到结果. 时间复杂度为
O
(
N
)
O(N)
O(N).
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
for (int i = 0; i < nums.size(); ++i)
if (nums[std::abs(nums[i]) - 1] > 0)
nums[std::abs(nums[i]) - 1] *= -1;
vector<int> res;
for (int i = 0; i < nums.size(); ++i)
if (nums[i] > 0)
res.push_back(i + 1);
return res;
}
};
扩展阅读
leetcode 上的解答:
The basic idea is that we iterate through the input array and mark elements as negative using nums[nums[i] -1] = -nums[nums[i]-1]
. In this way all the numbers that we have seen will be marked as negative. In the second iteration, if a value is not marked as negative, it implies we have never seen that index before, so just add it to the return list.
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> ret = new ArrayList<Integer>();
for(int i = 0; i < nums.length; i++) {
int val = Math.abs(nums[i]) - 1;
if(nums[val] > 0) {
nums[val] = -nums[val];
}
}
for(int i = 0; i < nums.length; i++) {
if(nums[i] > 0) {
ret.add(i+1);
}
}
return ret;
}
最为关键的是下面的解释:
大致意思可以理解为, 对于 nums
中的每个元素, 将 nums[i] - 1
对应的那个元素设置为负值, 表示那个元素已经被访问过, 之后判断 nums
中哪些值为正的, 如果为正, 说明对应的索引 i + 1
不存在在 nums
中.
A more detailed explanation for those who might still be confused:
This solution is using the relation between array index ([0, n-1])
and the given value range [1,n]
.
Each time when a new value X
is read, it changes the corresponding X
th number (value at index X-1
) into negative, indicating value X
is read for the first time. For example. using the given test case [4,3,2,7,8,2,3,1]
, when it comes to i = 2
in the first loop, this solution marks the 2nd
number (index = 1
), indicating we’ve found number 2
for the first time.
When we encounter a redundant number Y
, because we’ve marked the Yth
position (index Y -1
) when we saw Y
for the first time, the if clause won’t let us flip it again. This leaves the already marked Y
th number (number at index Y-1
) negative. For example, in the given test case, when i = 5
, val = |2| - 1 = 1
, nums[1] = -3 < 0
. No flip operation is needed because we’ve found value 2
before.
Looping through the 1st
loop takes
O
(
n
)
O(n)
O(n) time, flipping signs won’t take extra space.
The second loop checks the signs of the values at indices. If the sign at index P
is negative, it means value P + 1
is in the array. e.g. nums[0] = -4
, so value 0+1 = 1
is in the array. If the value at index Q
is positive, then value Q + 1
is not in the array. e.g. nums[4] = 8 > 0
, value 4 + 1 = 5
, we add 5
into the ret
list.