0%

原地hash

原地hash

可以用来找缺失的第一个数,或者是重复的数字。

缺失的第一个正整数

给定一个无重复元素的整数数组nums,请你找出其中没有出现的最小的正整数

进阶: 空间复杂度 $O(1)$ ,时间复杂度 $O(n)$ 。

数据范围:

$-2^{31} \le nums[i] \le 2^{31} - 1$

$0\le len(nums) \le 5 \times 10^5$

题解:

做法一:

原地hash操作,可以对存在的数字 $x$ ,在对应的下标 $nums[x]$ 上打上标记,这个标记可以是将其乘以 $-1$ 。

首先对 $ \le 0$ 的数变为 $n + 1$ 。

然后每次遇到 $nums[i]$ ,如果 $|nums[i]| \in [1, n]$ 则将 $nums[|nums[i - 1]| - 1]$ 变为 $-|nums[|nums[i - 1]| - 1]|$ 。注意需要取绝对值之后再取反。

再次遍历数组,如果遇到一个元素 $nums[i] \gt 0$ 则直接返回 $i + 1$ 。

做法二:

还可以使用交换的操作。把 $nums[i]$ 通过交换放到 $nums[nums[i] - 1]$ 的位置上,这样的话每个元素都有自己的位置。

首先,如果 $nums[i] \le 0$ ,将 $nums[i] = n + 1$ 。

然后每次遇到 $nums[i] \in [1, n]$ ,判断一下 $nums[nums[i] - 1] = nums[i]$ ,不相等的话就交换,直到二者相等。

最后再次遍历数组,如果遇到 $nums[i] \neq i + 1$ ,则直接返回 $i + 1$ 。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型vector
* @return int整型
*/
int minNumberDisappeared(vector<int>& nums) {
int n = nums.size();
int x = 0;
// [1, n]
for(int i = 0; i < n; ++i) {
if(nums[i] < 0) {
nums[i] = n + 1;
}
}
for(int i = 0; i < n; ++i) {
if(abs(nums[i]) >= 1 && abs(nums[i]) <= n) {
nums[abs(nums[i]) - 1] = -abs(nums[abs(nums[i]) - 1]);
}
}
for(int i = 0; i < n; ++i) {
if(nums[i] > 0) {
return i + 1;
}
}
return n + 1;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型vector
* @return int整型
*/
int minNumberDisappeared(vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n; ++i) {
if (nums[i] <= 0) {
nums[i] = n + 1;
}
}
for (int i = 0; i < n; ++i) {
while (nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
swap(nums[nums[i] - 1], nums[i]);
}
}

// for(int i = 0; i < n; ++i) {
// cout << nums[i] << ", ";
// }
// cout << endl;
int ans = n + 1;
for (int i = 0; i < n; ++i) {
if(nums[i] != i + 1) {
return i + 1;
}
}
return n + 1;
}
};

442. 数组中重复的数据

给你一个长度为 n 的整数数组 nums ,其中 nums 的所有整数都在范围 [1, n] 内,且每个整数出现 一次两次 。请你找出所有出现 两次 的整数,并以数组形式返回。

你必须设计并实现一个时间复杂度为 O(n) 且仅使用常量额外空间的算法解决此问题。

题解:

也是使用原地hash操作。

做法一:

每次将 $nums[|nums[i]| - 1]$ 变成相反数,如果之前是负数,那么就需要把他加入答案,因为出现了两次。如果之前是正数,那么就变为负数,因为第一次出现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution
{
public:
vector<int> findDuplicates(vector<int> &nums)
{
int n = nums.size();
vector<int> ans;
for (int i = 0; i < n; ++i)
{
if(nums[abs(nums[i]) - 1] < 0) {
ans.emplace_back(abs(nums[i]));
} else {
nums[abs(nums[i]) - 1] = -1 * nums[abs(nums[i]) - 1];
}
}
return ans;
}
};

做法二:

使用交换操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution
{
public:
vector<int> findDuplicates(vector<int> &nums)
{
int n = nums.size();
vector<int> ans;
for (int i = 0; i < n; ++i)
{
while(nums[nums[i] - 1] != nums[i]) {
swap(nums[nums[i] - 1], nums[i]);
}
}
for(int i = 0; i < n; ++i) {
if(nums[i] != i + 1) {
ans.emplace_back(nums[i]);
}
}
return ans;
}
};