想要精通算法和SQL的成长之路 - 存在重复元素
- 前言
- 一. 存在重复元素II
- 二. 存在重复元素III
- 2.1 基于红黑树增删改查
前言
想要精通算法和SQL的成长之路 - 系列导航
一. 存在重复元素II
原题链接
思路:
- 我们用
HashSet
存储元素,做到去重的效果。同时存储的元素个数,固定在k个。这个HashSet
相当于是一个滑动窗口了。 - 那么从左往右遍历,不断地往
HashSet
中塞元素,一旦超过容量,剔除滑动窗口最左侧元素。set.remove(nums[i - k - 1]);
- 遍历过程中,一旦发现当前元素存在于
HashSet
中,直接返回true
即可。
代码如下:
public boolean containsNearbyDuplicate(int[] nums, int k) {
HashSet<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
// 滑动窗口只存储k个元素,超过了,则移除
if (i > k) {
set.remove(nums[i - k - 1]);
}
if (set.contains(nums[i])) {
return true;
}
set.add(nums[i]);
}
return false;
}
二. 存在重复元素III
原题链接
我们先来一个最简单的思路,暴力法:
- 针对每个元素,作为滑动窗口的左边界。往后固定
indexDiff
长度的区间。 - 我们在
[left,left+indexDiff]
区间内遍历数组,计算差值。如果满足差值 <valueDiff
值,说明找到满足条件的结果,返回true
。
但是,这种操作,有着大量的重复计算,而且数组的无规律性,在最坏的情况下,我们得遍历整个长度为 k
的区间数组。那咋办呢?
思路如下:
- 我们可以维护一个有序并且长度为
k
的滑动窗口。那么对于该区间的任意一个数字num
。既然要满足差值 <valueDiff
值。那么在这个有序的集合当中。哪个数字最满足条件? - 第一种:小于等于
num
的最大值。第二种:和大于等于num
的最小值。即值num
左右两侧最靠近的数值是我们想要的。 - 那么对于有序的数组而言,想要查找上面两个数,用哪种方式最合适?二分法。
- 当然,我们还需要不断地维护这个滑动窗口对应的数据结构。
2.1 基于红黑树增删改查
下面来自百度百科的相关红黑树介绍:
- 红黑树是一种特化的AVL树(平衡二叉树),都是在进行插入和删除操作时通过若干次特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
- 而这个特定操作,对于红黑树而言,可以限制到最多三次。
- 它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在
O(log n)
时间内做查找,插入和删除,这里的n
是树中元素的数目。
针对上面的功能,红黑树都具备其查询:
- 查询不超过
num
的最大值:floor
函数。注意:如果找不到则返回null
。 - 查询超过
num
的最小值:ceiling
函数。注意:如果找不到则返回null
。
那么我们就不难写出代码:切记,对象和基本数据类型的比较,要判断null
,否则会报空指针哦~
// floorNum <= num ,最大值
Long floorNum = tree.floor(num);
// ceilingNum >=num ,最小值
Long ceilingNum = tree.ceiling(num);
if (floorNum != null && num - floorNum <= valueDiff) {
return true;
}
if (ceilingNum != null && ceilingNum - num <= valueDiff) {
return true;
}
由于题目的元素值存在以下范围:
因此我们在存储的时候,要把它转成Long
型。最终代码如下:
public boolean containsNearbyAlmostDuplicate(int[] nums, int indexDiff, int valueDiff) {
TreeSet<Long> tree = new TreeSet<>();
for (int i = 0; i < nums.length; i++) {
// int 转 long,因为限制问题
long num = nums[i] * 1L;
// floorNum <= num ,最大值
Long floorNum = tree.floor(num);
// ceilingNum >=num ,最小值
Long ceilingNum = tree.ceiling(num);
if (floorNum != null && num - floorNum <= valueDiff) {
return true;
}
if (ceilingNum != null && ceilingNum - num <= valueDiff) {
return true;
}
tree.add(num);
// 超过了滑动窗口大小
if (i >= indexDiff) {
tree.remove(nums[i - indexDiff] * 1L);
}
}
return false;
}