每日一题,防止痴呆 = =
一、题目大意
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-the-duplicate-number
二、题目思路以及AC代码
这道题刚看到的时候以为是用异或做了 = =,结果发现题目中说重复数的重复次数可能大于1次,这用异或就没有办法了,主要还是参照题解学习了三种解题方式。
思路一:二分
题目要求时间复杂度小于O(n2),所以无非就是O(nlogn)和O(n),一看到O(nlogn),我首先是想到快排,然后稀里糊涂就给过了(其实题目要求不能修改原数组的),所以就可以考虑二分。
但是对于二分,我们需要构造单调序列,这里可以考虑单调序列cnt,其中cnt[i]表示在nums数组中,小于等于i的数的个数,我们用target表示我们要找的重复数,那么可以想象当i < target的时候,cnt[i] 是小于等于i的(当且仅当重复数没有占用小于target数的位置时等号成立),当i >= target的时候,cnt[i]是大于i的,所以我们就可以根据以上情况进行二分,即如果cnt[mid] <= mid,那么target一定在右侧的数组中,如果cnt[mid] > mid,那么target一定在左侧的数组中或者就是mid.
按道理来说,应该先计算cnt数组,复杂度是O(n),然后再二分,复杂度是O(logn),总体复杂度O(n),但是由于题目要求不能使用额外的存储空间,所以这里就得现算cnt,造成结果的复杂度是O(nlogn).
思路二:二进制
和思路一差不多,这里的思路是考虑确定重复数的各个二进制位。即我们考虑重复数的第i位是否是1,我们令x表示nums数组中,第i位为1的总个数,y表示1~n中,第i位为1的总个数。那么如果x > y,那该位就是1,如果x <= y,那么该位就是0.
我们可以分情况考虑上述的问题。重复数我们分为重复1次,和重复多次。如果是重复一次,那么当重复数第i位为1的时候,则x = y + 1,当重复数为0的时候,则x = y,满足上述提到的条件。然后当重复数是重复多次的时候,必定要替代一个原来的数B,如果重复数第i位为1,B第i位为0,那么x > y+1,如果重复数第i位为1,B第i位为1,那么x = y+1 > y,如果重复数第i位为0,B第i位为1,那么x < y,如果重复数第i位为0,B第i位为0,那么x = y,综上所述,都满足上述提到的判定重复数第i位值得条件,即如果x > y,那该位就是1,如果x <= y,那么该位就是0.
思路三:判环
该题同样可以建立成一个图中判环的问题。我们令i -> nums[i]为边建图,那么由于有重复数的原因,所以势必有两个顶点同时指向同一个顶点,此时有环出现,而环的起点就是我们要找的重复数。
可以使用快慢指针来实现。
AC代码
上述代码我实现了一下思路一和思路三,列在下面。
思路一:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n_size = nums.size();
int l = 1, r = n_size - 1;
int mid = (l + r) >> 1;
while (l < r) {
mid = (l + r) >> 1;
int x = 0;
for (int i=0;i<n_size;i++) {
if (nums[i] <= mid) {
x++;
}
}
if (x <= mid) l = mid + 1;
else r = mid;
}
return l;
}
};
思路三:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow = 0, fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while(slow != fast);
slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
};
如果有问题,欢迎大家指正!!!