循环排序总结
可以用来处理数组中的数值限定在一定的区间的问题。
例题:
1.找到所有数组中消失的数字
(https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array/)
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。
找到所有在 [1, n] 范围之间没有出现在数组中的数字。
您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
题本身不难,使用哈希表很容易通过,但如果不使用额外空间和时间复杂度为O(n)时,就比较难了
思路:我们可以将数组中的每个数根据索引的大小,将对应索引的值都加上n,那么到最后,只有缺少的值对应的索引位置上的值没有加上n,再遍历一遍数组就可以得到缺少的值
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
vector<int> ans;
int n = nums.size();
for(int i=0;i<n;i++){
int pos = (nums[i]-1)%n;
nums[pos] += n;
}
for(int i=0;i<n;i++){
if(nums[i]<=n){
ans.push_back(i+1);
}
}
return ans;
}
};
2.寻找重复数
(https://leetcode-cn.com/problems/find-the-duplicate-number/)
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。
同样也可以使用哈希表解决,但有几种巧妙的方法
(1)二分
二分法的思路是取[left,right]的中间值mid,然后统计于是数组中 小于等于 mid的个数cnt,如果cnt的个数严格大于mid,那么答案就在[left,mid]中
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n = nums.size();
int l = 1,r = n-1;
while(l<r){
int mid = (l+r)/2;
int cnt = 0;
for(int i=0;i<n;i++){
cnt += (nums[i] <= mid);
}
//抽屉原理,如果小于等于mid的个数大于4,那么重复元素一定存在于[1,4]
if(cnt > mid) r = mid;
else l = mid + 1;
}
return l;
}
};
(2)位运算
位运算的思路是,先统计数组中每个元素在每一位上的个数x,然后统计[1,n]中每个元素在每一位上的个数y,如果x大于y,那么重复元素在该位是有值的。
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n = nums.size();
int bit_max = 31;
while(!((n-1) >> bit_max)){ //得到数组中数的最高位
bit_max--;
}
int ans = 0;
for(int i = 0 ;i <= bit_max;i++){
int x = 0,y = 0;
for(int j = 0;j < n;j++){
if(nums[j] & (1 << i)){
x++;
}
if(j && (j & (1 << i))){
y++;
}
}
//对存在的位进行或运算
if(x > y){
ans |= (1<<i);
}
}
return ans;
}
};
(3)快慢指针
如果数组中存在重复的元素,那么指针根据nums[]对应的索引来移动,那么最后一定会进入环中。不懂快慢指针的可以看前几天的题解。
快慢指针时间复杂度O(n),空间复杂度O(1).
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int fast = 0,slow = 0;
do{
slow = nums[slow];
fast = nums[nums[fast]];
}while(fast != slow);
slow = 0;
while(slow != fast){
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
};