leetcode 287. 寻找重复数
只会暴力超时🤡
法一:二分查找
官方题解:
class Solution {
public:
//先累计大小在 [1,⌊n/2⌋]之间的数字个数,如果重复数在这个范围内,则个数 > ⌊n/2⌋,
//否则可确定区间 (⌊n/2⌋,n]内存在重复数。即可通过二分查找求解。
int findDuplicate(vector<int>& nums) {
// 获取数组的长度
int n = nums.size();
// 初始化二分查找的左右边界,左边界为1,右边界为数组长度减1
int l = 1, r = n - 1;
// 初始化答案变量,用于存储可能的重复数
int ans = -1;
// 当左边界小于等于右边界时,继续循环
while (l <= r) {
// 计算中间值,相当于(l + r) / 2,但使用位运算避免溢出
int mid = (l + r) >> 1;
// 初始化计数器,用于统计数组中小于等于mid的元素个数
int cnt = 0;
// 遍历数组,统计小于等于mid的元素个数
for (int i = 0; i < n; ++i) {
if (nums[i] <= mid) cnt++;
}
// 根据统计结果调整查找范围
// 如果cnt <= mid,说明重复数在右半部分,移动左边界到mid+1
if (cnt <= mid) l = mid + 1;
// 否则,说明重复数在左半部分,移动右边界到mid-1,并记录当前mid为可能的重复数
else {
r = mid - 1;
ans = mid;
}
}
// 返回找到的重复数
return ans;
}
};
另一种写法:
class Solution {
public:
//先累计大小在 [1,⌊n/2⌋]之间的数字个数,如果重复数在这个范围内,则个数 > ⌊n/2⌋,
//否则可确定区间 (⌊n/2⌋,n]内存在重复数。即可通过二分查找求解。
int findDuplicate(vector<int>& nums) {
// 初始化查找范围的最小值为1,因为数组中的数字从1开始
int min = 1;
// 初始化查找范围的最大值为数组的长度,因为数组长度是n+1,数字范围是1到n
int max = nums.size() - 1;
// 当最小值小于最大值时,继续查找
while (min < max) {
// 计算中间值,作为当前查找范围的中间点
int mid = (min + max) / 2;
// 统计数组中在[min, mid]范围内的数字个数
int cnt = 0;
for (int &v : nums) {
if (v >= min && v <= mid) cnt++;
}
// 如果统计的个数超过范围的长度,说明该范围内有重复数字
if (cnt > mid - min + 1) {
// 将最大值调整为mid,缩小查找范围到左半部分
max = mid;
} else {
// 否则,将最小值调整为mid+1,查找范围移动到右半部分
min = mid + 1;
}
}
// 当循环结束时,min即为重复的数字
return min;
}
};
法二:快慢指针,思路类似 leetcode142. 环形链表 II
class Solution {
public:
//环的存在:由于数组中存在重复数字,可以将数组视为一个链表结构,其中重复数字是环的入口点。slow 和 fast 指针最终会在环内相遇。
//环的入口点:当 slow 和 fast 相遇后,从起点和相遇点分别移动两个指针,每次一步,它们会在环的入口点相遇。这是因为从起点到入口点的距离等于从相遇点到入口点的距离。
int findDuplicate(vector<int>& nums) {
// 初始化两个指针,slow和fast,都从数组的起始位置开始
int slow = 0;
int fast = 0;
// 移动slow指针一步,fast指针两步
slow = nums[slow];
fast = nums[nums[fast]];
// 当slow和fast相遇时,说明找到了环
while (slow != fast) {
slow = nums[slow];
fast = nums[nums[fast]];
}
// 初始化另一个指针pre1从起点开始,pre2从相遇点开始
int pre1 = 0;
int pre2 = slow;
// 当pre1和pre2相遇时,相遇点就是重复的数字
while (pre1 != pre2) {
pre1 = nums[pre1];
pre2 = nums[pre2];
}
// 返回重复的数字
return pre1;
}
};