数组中重复(缺失)的数字
剑指 Offer 03 数组中重复的数字
题目描述:剑指 Offer 03 数组中重复的数字
分析
解法一
-
本题的考点:数学,哈希表。
-
本题中最常规的做法可以使用哈希表统计每个数据出现的次数,如果存在某个数据出现次数超过两次,返回即可。
-
这里采用另一种做法,因为所有数据范围都在
[0~n-1]
之间,每次遍历到一个数据x=nums[i]
,如果nums[x]
位置的数据为正数,则将nums[x]
上的数据去相反数;如果nums[x]
位置的数据为负数,则说明x
第二次出现,返回x
即可。 -
如果遍历结束都没返回值,则说明
[1~n-1]
最多出现了一次,则说明0
出现了多次,返回0
即可。
解法二
-
我们让下标
i
放置的数据为i
,类似于桶排序。 -
依次考察每个元素
nums[i]
,循环让nums[i]
放置到下标为nums[i]
的位置,在交换后如果还有nums[i]!=i
,说明下标为nums[i]
的位置已经放置了nums[i]
,因此nums[i]
重复,返回nums[i]
即可。
代码
- C++
// 解法一
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
for (int i = 0; i < nums.size(); i++) {
int x = abs(nums[i]);
if (nums[x] < 0) return x;
nums[x] *= -1;
}
return 0;
}
};
// 解法二
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
for (int i = 0; i < nums.size(); i++) {
while (nums[nums[i]] != nums[i]) swap(nums[nums[i]], nums[i]);
if (nums[i] != i) return nums[i];
}
return 0;
}
};
时空复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),
n
为数组长度。 -
空间复杂度: O ( 1 ) O(1) O(1)。
AcWing 14. 不修改数组找出重复的数字
问题描述
分析
解法一
-
因为不能修改原始数组,因此剑指 Offer 03 数组中重复的数字的方法,可以使用哈希表统计每个元素出现次数,但这样空间复杂度是 O ( n ) O(n) O(n)的。
-
因为要求空间是 O ( 1 ) O(1) O(1)的,本题需要使用二分解决,相当于用时间换空间。
-
答案在区间
[1,n]
中,因此初始二分区间是[1, n]
,取中点mid=(l + r)/2
,统计在[l, mid]
中数据的个数,记为s
,如果s>mid-l+1
,根据抽屉原理在[l, mid]
中必然存在重复的数,否则在[mid+1, r]
中必然存在重复的数。
解法二
-
可以使用快慢指针解决,类似于题目Leetcode 0142 环形链表 II。
-
我们首先考虑如何建图,首先图中有
0~n
一共n+1
个点,对于数组中的元素nums[i]
,则我们在图中连一条i->nums[i]
的边,因为数组nums
长度为n+1
,因此有n+1
条边,根据抽屉原理,则必有环。并且0
不再环中,因为没有边会指向0
。例如题目中的第一个样例:
- 对于这个环的入口,一定会有两条边指向它,这说明存在两个索引,对应的位置存储的是同一个数,因此环的入口就是我们要找到数。可以转换为LC142。
代码
- C++
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
int l = 1, r = nums.size() - 1; // [l, r] 初始为 [1, n]
while (l < r) {
int mid = l + r >> 1;
int s = 0;
for (auto x : nums) s += x >= l && x <= mid;
if (s > mid - l + 1) r = mid; // 说明[l, mid]中的数目多于坑数,根据抽屉原理,其中必有重复元素
else l = mid + 1;
}
return r;
}
};
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
int a = 0, b = 0;
while (true) {
a = nums[a], b = nums[nums[b]];
if (a == b) {
a = 0;
while (a != b) a = nums[a], b = nums[b];
return a;
}
}
}
};
Leetcode 0041 缺失的第一个正数
分析
-
本题的考点:桶排序。
-
分为两步:
(1)使用桶排序,对原数组进行操作,使得1出现在
nums[0]
的位置上,2出现在nums[1]
的位置上,…,n
出现在nums[n-1]
的位置。因此,如果 0 < n u m s [ i ] ≤ n 0<nums[i] \le n 0<nums[i]≤n,则nums[i]
应该出现在nums[nums[i] - 1]
的位置上。(2)上述过程结束后,找到第一个不在应在位置上的1到
n
的数,即当nums[i] != i+1
时返回i+1
,如果全部满足nums[i] == i+1
,则说明整个数组中的元素是1~n
,返回n+1
即可。 -
参考网址:网址。
代码
- C++
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
// (1) 让所有在1~n中的数据t放置到nums[t - 1]中
for (int i = 0; i < n; i++) {
int &t = nums[i]; // t应该放到nums[t - 1]的位置
while (t > 0 && t <= n && nums[t - 1] != t)
swap(t, nums[t - 1]); // 此时下标为t-1的位置正确放置了t, 但是nums[i]换过来的数据不一定是i+1, 因此是while
}
// (2) 找出不满足nums[i] == i+1的位置,返回i+1; 如果都满足,返回n+1
for (int i = 0; i < n; i++)
if (nums[i] != i + 1)
return i + 1;
return n + 1;
}
};
- Java
class Solution {
public int firstMissingPositive(int[] nums) {
int n = nums.length;
// (1) 让所有在1~n中的数据nums[i]放置到nums[nums[i] - 1]中
for (int i = 0; i < n; i++) {
// nums[i]>0且nums[i]<=n时, nums[i]应该放到nums[nums[i] - 1]的位置
while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i])
swap(nums, i, nums[i] - 1);
}
// (2) 找出不满足nums[i] == i+1的位置,返回i+1; 如果都满足,返回n+1
for (int i = 0; i < n; i++)
if (nums[i] != i + 1)
return i + 1;
return n + 1;
}
private void swap(int[] nums, int i, int j) {
int t = nums[i]; nums[i] = nums[j]; nums[j] = t;
}
}
- Python
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
n = len(nums)
for i in range(0, n):
while 0 < nums[i] <= n and nums[i] != nums[nums[i] - 1]:
nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]
for i in range(0, n):
if nums[i] != i + 1:
return i + 1
return n + 1
时空复杂度分析
-
时间复杂度: O ( n ) O(n) O(n)。因为每次交换,会将一个数放在正确的位置上,所以总共最多执行
n
次,所以总时间复杂度 O ( n ) O(n) O(n)。 -
空间复杂度: O ( 1 ) O(1) O(1)。使用原数组。
Leetcode 0287 寻找重复数
题目描述:Leetcode 0287 寻找重复数
分析
-
本题的考点:快慢指针。
-
本题本质上就是Leetcode 0142 环形链表 II。
-
我们首先考虑如何建图,首先图中有
0~n
一共n+1
个点,对于数组中的元素nums[i]
,则我们在图中连一条i->nums[i]
的边,因为数组nums
长度为n+1
,因此有n+1
条边,根据抽屉原理,则必有环。并且0
不再环中,因为没有边会指向0
。例如题目中的第一个样例:
- 对于这个环的入口,一定会有两条边指向它,这说明存在两个索引,对应的位置存储的是同一个数,因此环的入口就是我们要找到数。可以转换为LC142。
代码
- C++
class Solution {
public:
int findDuplicate(vector<int> &nums) {
int a = 0, b = 0; // a为慢指针,b为快指针
while (true) {
a = nums[a], b = nums[nums[b]];
if (a == b) {
a = 0;
while (a != b) a = nums[a], b = nums[b];
return a;
}
}
}
};
- Java
class Solution {
public int findDuplicate(int[] nums) {
int a = 0, b = 0;
while (true) {
a = nums[a];
b = nums[nums[b]];
if (a == b) {
a = 0;
while (a != b) {
a = nums[a];
b = nums[b];
}
return a;
}
}
}
}
时空复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),
n
为数组长度。 -
空间复杂度: O ( 1 ) O(1) O(1)。
Leetcode 0442 数组中重复的数据
分析
-
本题的考点:数学。
-
因为所有数据都是在
1~n
之间的,对于每个数据x
,我们将下标为x-1
的位置每次取相反数,如果这个对应位置的数据变为正数,说明出现了两次。
代码
- C++
class Solution {
public:
vector<int> findDuplicates(vector<int>& nums) {
vector<int> res;
for (auto x : nums) {
int p = abs(x) - 1;
nums[p] *= -1;
if (nums[p] > 0) res.push_back(abs(x));
}
return res;
}
};
- Java
class Solution {
public List<Integer> findDuplicates(int[] nums) {
List<Integer> res = new ArrayList<>();
for (int x : nums) {
int p = Math.abs(x) - 1;
nums[p] *= -1;
if (nums[p] > 0) res.add(Math.abs(x));
}
return res;
}
}
时空复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),
n
为数组长度。 -
空间复杂度: O ( 1 ) O(1) O(1)。
Leetcode 0448 找到所有数组中消失的数字
分析
-
本题的考点:数学。
-
两次遍历数组:第一次,对于遍历到的数据
x
,将nums[x-1]
位置的数据变为负数;第二次使用i
从1
遍历到n
,如果nums[i-1]
为正数,说明i
没在数组中出现过,记到答案中。最终返回答案即可。
代码
- C++
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
for (auto x : nums) {
x = abs(x);
if (nums[x - 1] > 0) nums[x - 1] *= -1;
}
vector<int> res;
for (int i = 1; i <= nums.size(); i++)
if (nums[i - 1] > 0)
res.push_back(i);
return res;
}
};
- Java
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
for (int x : nums) {
x = Math.abs(x);
if (nums[x - 1] > 0) nums[x - 1] *= -1;
}
List<Integer> res = new ArrayList<>();
for (int i = 1; i <= nums.length; i++)
if (nums[i - 1] > 0)
res.add(i);
return res;
}
}
时空复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),
n
为数组长度。 -
空间复杂度: O ( 1 ) O(1) O(1)。
Leetcode 0645 错误的集合
题目描述:Leetcode 0645 错误的集合
分析
-
本题的考点:数学。
-
因为所有数据都是在
1~n
之间的,对于每个数据x
,我们将下标为x-1
的位置每次取相反数,如果这个对应位置的数据最终是正数,说明出现了两次或者没有出现。 -
如何区分这两个数呢(一个没有出现,一个出现了两次)?可以采取两次遍历,第一次求出现两次的数,第二次遍历数组,如果对应位置的数大于0且不等于出现两次的数,说明是缺失的数。
代码
- C++
class Solution {
public:
vector<int> findErrorNums(vector<int>& nums) {
vector<int> res(2);
for (auto x : nums) {
int k = abs(x);
if (nums[k - 1] < 0) res[0] = k;
nums[k - 1] *= -1;
}
for (int i = 0; i < nums.size(); i++)
if (nums[i] > 0 && i + 1 != res[0]) {
res[1] = i + 1;
break;
}
return res;
}
};
- Java
class Solution {
public int[] findErrorNums(int[] nums) {
int[] res = new int[2];
for (int x : nums) { // x 大小在[1...n]之间
int k = Math.abs(x);
if (nums[k - 1] < 0) res[0] = k;
nums[k - 1] *= -1;
}
for (int i = 1; i <= nums.length; i++)
if (nums[i - 1] > 0 && i != res[0]) {
res[1] = i;
break;
}
return res;
}
}
- Python
class Solution:
def findErrorNums(self, nums: List[int]) -> List[int]:
res = [0] * 2
for x in nums:
k = abs(x)
if nums[k - 1] < 0:
res[0] = k
nums[k - 1] *= -1
for i in range(len(nums)):
if nums[i] > 0 and i + 1 != res[0]:
res[1] = i + 1
break
return res
时空复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),
n
为数组长度。 -
空间复杂度: O ( 1 ) O(1) O(1)。