大家好,我是 @负雪明烛。👈👈 点击关注,优质题解不间断。
题目大意
题目让找出数组中出现两次的数字。要求时间复杂度是 $O(N)$,空间复杂度是 $O(1)$。
条件:
- 数组长度是 ,每个数据的取值范围是
- 每个数字只出现一次或者两次
- 要求时间复杂度 $O(N)$,空间复杂度是 $O(1)$
解题方法
原地修改数组
遇到这个题,你的第一思路是什么呢?
我想一定是使用 set
或者 hashmap
保存已经出现过的数字、或者数字出现的次数。
但这就不符合题目的空间复杂度是 $O(1)$,即不使用额外空间的限制条件。
我的第二个思路是对数组排序,排序后相同的数字会相邻。可是排序的时间复杂度是 $O(N * log(N))$不符合题目的时间复杂度$O(N)$。
所以,只能祭出大杀器:「原地修改数组」了。
其实这个思路是沿着 hashmap
的思路演化出来的,只要遇到过一次就学会了。
我们注意题目给出的条件:
- 数组长度是 ,每个数据的取值范围是
我们知道一个数组有 下标
和 值
两个概念,根据下标
获取到值
。
本题中,数组中数字的取值范围 ,正好与下标的范围 对应。
因此,就有一个思路,对于 nums[i]
,我们将下标 的位置的数字进行映射(还要能映射回去)。
映射方法通常有两种:
- 取反
- 增加偏移量
方法一:取反
- 从起始位置进行遍历,每次将下标为 的数字取反;
- 当遍历到 为负数,需要忽略其负号。
- 若发现下标为 的数字已经是负数,说明之前出现过同样的数字 ,即找到了重复数字;
动画图解如下:
代码如下:
class Solution(object):
def findDuplicates(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
ans = []
for num in nums:
if nums[abs(num) - 1] < 0:
ans.append(abs(num))
nums[abs(num) - 1] *= - 1
return ans
class Solution {
public:
vector<int> findDuplicates(vector<int>& nums) {
const int N = nums.size();
vector<int> res;
for (int i = 0; i < N; i++) {
if (nums[abs(nums[i]) - 1] < 0)
res.push_back(abs(nums[i]));
nums[abs(nums[i]) - 1] *= -1;
}
return res;
}
};
方法二:增加偏移量
除了取反之外,我们还可以增加一个偏移量,只要映射后与原数组的范围区分开就行。
思路和取反一样,只是为了映射后与映射前的数组不混淆。
比如,本题中数字的范围是 ,我们可以增加一个偏移量 ,即映射到了一个新的数组空间。
做法:
- 从起始位置进行遍历,每次将下标为 的数字 ;
- 遍历到 超过 ,需要忽略将其 恢复原值;
- 若发现下标为 的数字已经是超过 ,说明之前出现过同样的数字 ,即找到了重复数字;
代码如下:
class Solution {
public:
vector<int> findDuplicates(vector<int>& nums) {
vector<int> res;
for (int num : nums) {
if (nums[(num % 100000) - 1] > 100000) {
res.push_back(num % 100000);
}
nums[(num % 100000) - 1] += 100000;
}
return res;
}
};
复杂度
- 这个题是技巧题目,看懂了我的题解以后,以后遇到同样的题目,应该就会了。
我是 @负雪明烛 ,刷算法题 1000 多道,写了 1000 多篇算法题解,收获阅读量 300 万。
关注我,你将不会错过我的精彩动画题解、面试题分享、组队刷题活动,进入主页 @负雪明烛 右侧有刷题组织,从此刷题不再孤单。
- 在刷题的时候,如果你不知道该怎么刷题,可以看 LeetCode 应该怎么刷?
- 如果你觉得题目太多,想在短时间内快速提高,可以看 LeetCode 最经典的 100 道题。
- 送你一份刷题的代码模板:【LeetCode】代码模板,刷题必会
- 我写的 1000 道 LeetCode 题解,都在这里了,免费拿走。