【LeetCode & 剑指offer 刷题笔记】目录(持续更新中...)
287. Find the Duplicate Number
Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.
Example 1:
Input: [1,3,4,2,2]Output: 2
Example 2:
Input: [3,1,3,4,2]
Output: 3
Note:
-
You must not modify the array (assume the array is read only).
-
You must use only constant, O(1) extra space.
-
Your runtime complexity should be less than O(n2).
-
There is only one duplicate number in the array, but it could be repeated more than once.
//问题:查找数组中的重复数字(长度为n+1,所有数字在1~n范围内,找出数组中任意一个重复数字,不能修改数组)
/*
方法一:sort
若可以改变数组,可以直接sort,O(nlogn),O(1)
若不能改变数组,可以复制vector,再sort,O(nlogn), O(n)
*/
class Solution
{
public:
int findDuplicate(vector<int>& nums)
{
if(nums.empty()) return -1; //表示没有重复数字
sort(nums.begin(), nums.end());
for(int i = 1; i<nums.size(); i++)
{
if(nums[i] == nums[i-1]) return nums[i];
}
return -1;
}
};
/*
方法二:哈希表
将元素当做key值,如果之前出现过了,则返回重复数字
O(n), O(n)
*/
class Solution
{
public:
int findDuplicate(vector<int>& nums)
{
if(nums.empty()) return -1; //表示没有重复数字
unordered_set<int> seen;
for(int num:nums)
{
if(seen.find(num) != seen.end()) return num; //如果在set中找到了该值,说明重复了
else seen.insert(num); //否则插入key值
}
return -1;
}
};
/*
方法三:也可以参见《剑指offer》P39中的解法(下标比较交换法) O(n),O(1)但会改变数组
问题:长度为n的数组中所有数字在0~n-1范围内,找出重复的数字
例:1 2 0 3 2 4
i=0,
2 1 0
0 1 2
i=1...
*/
#include <algorithm>
class Solution {
public:
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
bool duplicate(int a[], int length, int* duplication)
{
//1.数组异常情况处理
if(a == nullptr || length < 0) return false;
//2.数组值不符合条件时的处理
for(int i = 0; i < length; i++)
{
if(a[i]<0 || a[i]>length-1) return false;
}
/*
3. 比较a[i]与i
如果相等,i++
如果不相等,比较a[i]与a[a[i]],若相等,为重复数,退出;若不相等就交换。
*/
for(int i = 0; i<length; i++)
{
while(a[i] != i)
{
if(a[i] == a[a[i]])
{
*duplication = a[i];
return true; //这里也可以返回重复的数字
}
else
swap(a[i], a[a[i]]); //每个数字最多交换2次(第一次为被交换方,第二次为交换方,到应处位置),故整个程序复杂度为O(n),o(1)
//a[i]被换到它应处的位置
}
}
return false;
}
};
/*
方法三:快慢指针法 O(n),O(1) 且不用改变数组
将索引看做当前结点地址,将存储数看做指向下一个结点的指针,则重复数字即为环入口(索引或结点地址)
把第一个结点当做头结点
例子:
1 4 3 5 2 2
索引分别为0、1、2、3、4、5
0 1 4 2 3 5
1 -> 4 -> 2 -> 3 -> 5 -> 2
↑←-------←↓
注:
(1)如果在多一个重复数字2,则会多一个结点指向2,但是该结点永远无法被访问到,因为没有结点指向它
(2)如果数组中不存在重复数字,则为循环链表,相当于循环链表,这个时候需返回-1.
(3)如果多个数字重复,只有最前面的重复数字构成环,其他重复数字不会在链表中,所以只能检测一个重复数字
(4)如果数组中有数字0,则该结点会指向头结点,从而形成循环链表,而其他结点会被丢失(所以题目限定不包含0,如果要包含0或负数的话,可以把整个数组预处理一遍,为负数时说明输入非法,为0时,可以将各数加1)
*/
class Solution
{
public:
int findDuplicate(vector<int>& nums)
{
if(nums.empty()) return -1; //表示没有重复数字
int slow = nums[0];
int fast = nums[nums[0]];
while(slow != fast) //假设存在重复数字,则会在环内相遇,假设不存在重复数字,形成循环链表,在头结点相遇
{
slow = nums[slow];
fast = nums[nums[fast]];
}
int entry = 0;
if(entry == slow) return -1; //如果不存在重复数字,为循环链表(环入口在起始位置),则返回-1
while(entry != slow)
{
entry = nums[entry];
slow = nums[slow];
}
return entry;
}
};