220. 存在重复元素 III去力扣刷题吧!
在整数数组 nums
中,是否存在两个下标 i
和 j
–> ① nums[i]
和 nums[j]
的差的绝对值小于等于 t
–> ② 且 i
和 j
的差的绝对值也小于等于 k
。
问题分析
方法一. 滑动窗口 + TreeSet
- 由于在数组中需要满足两个下标的距离在
k
范围呢,因此可以结合滑动窗口的思想,只是这边的滑动窗口固定了窗口大小为k + 1
,我们用一个TreeSet维护该窗口,当新加入元素num
时,判断窗口中是否有元素在[num - t, num + t]
范围内,有则返回true
,无则将num
加入窗口,同时移出最左侧的元素。 - TreeSet的妙用:
set.ceiling(num)
Treeset中的上限函数,该方法可以返回一个set中大于等于num的最小值,在判断窗口中是否有元素在[num - t, num + t]
范围内若存在一个值在该范围内即可返回true
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
//采用long是因为测试用例中有Integer越界问题
TreeSet<Long> set = new TreeSet<>();
int n = nums.length;
for (int i = 0; i < n; i++) {
if (i > k) {
set.remove((long)nums[i - k - 1]);
}
// ceiling Treeset中的上限函数,set中大于某个值的最小值
Long low = set.ceiling((long) nums[i] - t);
//是否找到了符合条件的数
if (low != null && low <= (long)nums[i] + t) {
return true;
}
set.add((long) nums[i]);
}
return false;
方法二. 桶排序
- 同时仍是基于滑动窗口的思想。
- 我们将每个桶的大小设置为
t + 1
,每次判断当前num
需要放进的桶编号中是否有值,或者相邻的桶中是否有元素在[num - t, num + t]
范围内。使用map
存储,key
表示桶编号,value
表示桶中已存在的数字。 - 同时在移动过程中需要将
i - k - 1
的值移出桶外,因为该值已经不会影响到当前num
值 - 获取桶编号采用
num / (t + 1)
的方式,但是在数组中存在负数的情况,则会存在靠近0
的负数
/ (t + 1)
时桶编号也为0
, 因此采用(num + 1) / (t + 1) - 1
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
if (t < 0) {
return false;
}
HashMap<Long, Long> map = new HashMap<>();
int n = nums.length;
long w = t + 1; // 一个桶里边数字范围的个数是 t + 1
for (int i = 0; i < n; i++) {
//删除窗口中第一个数字
if (i > k) {
map.remove(getId(nums[i - k - 1], w));
}
//得到当前数的桶编号
long id = getId(nums[i], w);
//当前桶中已有值
if (map.containsKey(id)) {
return true;
}
//相邻的桶中有在[num - t, num + t]内
if (map.containsKey(id + 1) && map.get(id + 1) - nums[i] < w) {
return true;
}
if (map.containsKey(id - 1) && nums[i] - map.get(id - 1) < w) {
return true;
}
map.put(id, (long) nums[i]);
}
return false;
}
private long getId(long num, long w) {
if (num >= 0) {
return num / w;
} else {
return (num + 1) / w - 1;
}
}
复杂度分析
- 滑动窗口+TreeSet:
O(nlog(k))
TreeSet
的ceiling
方法的时间复杂度都是O(log(n))
- 桶排序:
O(n)
在HashMap
中取值是常数级的,同时我们只需要遍历一遍数组