在LeetCode中有四道查找数组中重复数字的类似的题目,先总结一波:
LeetCode 217:Contains Duplicate
LeetCode 219:Contains Duplicate II
LeetCode 220:Contains Duplicate III
LeetCode 287:Find the Duplicate Number
一. Contains Duplicate
给定一个整数数组,判断该数组是否存在重复元素;当数组中某元素至少存在两个,则返回true,如果每个元素都不相等,则返回false。
范例1:
输入:[1,2,3,1]
输出: true
范例2:
输入:[1,2,3,4]
输出: false
范例3:
输入: [1,1,1,3,3,4,3,2,4,2]
输出: true
解题思路:这个就很简单,主要思路有:
(1)将数组排序,判断前后两个元素是否一样,如果一样则返回true,否则返回false;
(2)方法(1)对数组进行排序,改变了数组元素的位置;若要求不能修改数组元素,可以创建一个辅助HashSet,判断HashSet中是否已经存在该元素,存在则返回true,否则返回false,并将其加入在HashSet中。
代码实现:
class Solution {
public boolean containsDuplicate(int[] nums) {
int len = nums.length;
if(len==0||len==1)
return false;
//方法二,借助HashSet
HashSet<Integer> re = new HashSet<Integer>();
for(int i = 0;i<len;i++){
if(re.contains(nums[i]))
return true;
re.add(nums[i]);
}
/*
方法一:首先将数组排序
Arrays.sort(nums);
for(int i = 0;i<len-1;i++){
if(nums[i]==nums[i+1])
return true;
}*/
return false;
}
}
二. Contains Duplicate II
给定一个数组nums和一个整数k,是否存在两个不相等的整数 i 和 j,使得nums[i] == nums[j],并且i和j之间的距离最多为k。
范例1:
输入:[1,2,3,1],k=3
输出: true
范例2:
输入:[1,0,1,1], k = 1
输出: true
范例3:
输入:[1,2,1], k = 0
输出: false
解题思路:
这道题在题目一的基础上增加了k,使得需要满足条件|i-j| <=k;
那么可以借助HashMap,key为nums[i],value为i,用来记录数组元素以及在数组中的位置;
那么当num[j]已经在HashMap中时,即nums[i] == nums[j],那么判断是否满足 j-i<=k,如果满足,则直接返回true;如果不满足,则需要用(nums[j],j)替换(nums[i],i),因为之后可能还会出现元素等于nums[i],如范例2;
代码实现:
class Solution {
public boolean containsNearbyDuplicate(int[] nums, int k) {
int len = nums.length;
if(len<=1||K==0){
return false;
}
HashMap<Integer, Integer> temp = new HashMap<Integer, Integer>();
for(int i = 0;i<len;i++){
if(temp.containsKey(nums[i])&&(i-temp.get(nums[i]))>k){
temp.put(nums[i], i);
}else if(temp.containsKey(nums[i])&&(i-temp.get(nums[i]))<=k)
return true;
else
temp.put(nums[i], i);
}
return false;
}
}
三. Contains Duplicate III
给定整数数组,是否存在两个不相等的 i 和 j 使得nums[i] 和nums[j] 的差值至多为 t ,i 和 j的距离至多为k。
范例1:
输入: [1,2,3,1], k = 4, t = 0
输出: true
范例2:
输入: [1,0,1,1], k = 1, t = 0
输出: true
范例 3:
输入: [4,2], k = 2, t = 1
输出: false
解题思路:
利用滑动窗口和TreeSet中的floor()方法和ceiling()方法;
滑动窗口:将TreeSet中的元素数量控制到最多为k个,使得|i-j|<=k成立;
floor()方法返回TreeSet中小于等于nums[i]的元素中的最大值,如果TreeSet中不存在num[i],则返回null;
ceiling()方法返回TreeSet大于等于nums[i]的元素中的最小值,如果不存在nums[i],则返回null;
代码实现:
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
int len = nums.length;
if(len<=1 || k<=0)
return false;
TreeSet<Integer> temp = new TreeSet<Integer>();
for(int i = 0;i<len;i++){
//找到小于等于nums[i]的元素中的最大值
if(temp.floor(nums[i])!=null){
long farMin = temp.floor(nums[i]);
if(nums[i]-farMin<=t)
return true;
}
//找到大于等于nums[i]的元素中的最小值
if(temp.ceiling(nums[i])!=null){
long leastMax = temp.ceiling(nums[i]);
if(leastMax - nums[i]<=t)
return true;
}
temp.add(nums[i]);
//设置treeset中的元素数量最多为k个
if(temp.size()>k){
temp.remove(nums[i-k]);
}
}
return false;
}
}
四. Contains Duplicate III
给定一个包含n+1个整数的数组,其中每个整数的范围为1~n,判断是否至少存在一个重复元素,并找到该元素,假设该数组中只存在一个重复元素。
范例1:
输入: [1,3,4,2,2]
输出: 2
范例2:
输入: [3,1,3,4,2]
输出: 3
注意:
不能修改数组,该数组为只读数组;
空间复杂度为O(1);
时间复杂度小于(n^2);
该数组中只存在一个重复元素,但该元素的个数超过2个;
解题思路:
首先忽略注意中的几个条件,解决方案有:
(1)如题目一中的解法,对数组进行排序,或者创建一个额外的hashset,然后进行判断即可;
(2)因为数组中元素的范围为1~n,那么数组排序后,若不存在重复的元素,则数字 i 一定出现在下标为 i 的位置上(这里假设 下标从1开始);若存在重复元素,那么下标为i的位置上可能不是数字i;
所以对数组进行重排序,如果位置i上的元素为i,那么继续扫描,如果不为i,则和nums[i]位置上的元素进行比较,如果和nums[i]位置上的元素相等,则找到重复元素,如果不相等,将和nums[i]位置上的元素进行交换,即将nums[i]放置到i上,接下来重复比较交换的过程,直到找到重复的元素;
接着看注意中的几个条件,不能改变数组元素,并且空间复杂度为O(1),那么这就是说不能重排数组以及借助额外的辅助空间;
时间复杂度要小于O(n^2),那么直接暴力破解也不行;
所以这道题目的解决方案为:使用二分法的思想。因为数组中的元素大小范围为1~n,那么我们将其该范围从正中间分成两个部分,1~m和m+1~n,那么该数组中的元素在1~m范围内的元素个数,如果大于m,那么重复元素一定存在于前半部分,否则存在在后半部分;接着继续一分为二进行判断,直到找打重复元素位置
代码实现:
class Solution {
public int findDuplicate(int[] nums) {
int len = nums.length;
int start = 1;
int end = len;
int half = 0;
while(start<end){
int middle = start + (end-start)/2;
half = count(nums,start,middle);
if(half > middle-start+1){
end = middle;
}else{
start = middle+1;
}
}
if(start==end){
half = count(nums,start,end);
if(half>1)
return start;
}
return -1;
}
//计算数组中大小范围为start~end的元素个数
public int count(int[] nums, int start, int end){
if(start>end)
return 0;
int re = 0;
for(int i = 0;i<nums.length;i++){
if(nums[i]>=start&&nums[i]<=end){
re ++;
}
}
return re;
}
}
时间复杂度为O(nlogn),空间复杂度为O(1)