题目描述
给定一个长度为 n+1 的数组nums,数组中所有的数均在 1∼n 的范围内,其中 n≥1。
请找出数组中任意一个重复的数,但不能修改输入的数组。
如果只能使用 O(1) 的额外空间,该怎么做呢?
样例
给定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。
返回 2 或 3。
算法1
(暴力枚举) $O(n^2)$
两重循环就完事了
测了三次都平均是22ms 和二分一样快,可能提前结束的早?
时间复杂度 $O(n^2)$
C++ 代码
class Solution {
public:
int duplicateInArray(vector<int>& a) {
for(int i = 0; i < a.size(); i++)
{
for(int j = i+1 ; j < a.size();j++)
{
if(a[j] == a[i])return a[i];
}
}
}
};
算法2
(二分) $O(n)$
由于nums只会出现1~n这n-1个数在加上一个属于1~n的数
找到的这个重复元素要么在左半边,要么在右半边
统计出现在l~mid的数出现次数
次数大于区间长度,说明该区间上有重复元素,包含mid
否则在右半边mid+1~r
最后l>=r循环结束
如果nums的长度是奇数
假设区间是0~4
重复数字在右边。
一开始l = 1,r = 4,mid = 2
后来l = 3,r = 4,mid = 3
后来l = mid+1=4 >= r = 4结束
返回r就完事了
重复的数字1在左边。
一开始l = 1,r = 4,mid = 2
后来 l = 1,r = 2,mid = 1
后来 l = 1,r = 1结束
且此时只有两个相同的元素下标为0或1 返回r就完事了
如果nums的长度是偶数
假设区间是0~3
重复的数字在右边。
一开始l = 1,r = 3,mid = 2
后来l = mid+1 = 3 >= r结束
此时返回r就完事了
重复数字在左边
一开始l = 1,r = 3,mid = 2
后来l = 1,r = 2,mid = 1
后来l = 1,r = 1,mid = 1结束
且此时只有两个相同的元素下标为0或1 返回r就完事了
总之 返回 r 就完事了
时间复杂度O(nlogn)
参考文献 y总 剑指offer
C++ 代码
class Solution {
public:
int duplicateInArray(vector<int>& nums) {
int l = 1, r = nums.size()-1;//所有不重复的数字1~n
while(l < r)
{
int mid = l + r >> 1;
//统计l到mid的数出现的次数
int s = 0;
for (auto x : nums) s += x >= l && x <= mid;
//出现在左半边的次数多于l~mid的话,则重复出现的那一个数字一定在左边,且mid符合要求
if (s > mid - l + 1) r = mid;
else l = mid + 1;
}
return r;
}
};