题目
给你一个整数数组 nums 和两个整数 k 和 t 。请你判断是否存在 两个不同下标 i 和 j,使得abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。
如果存在则返回 true,不存在返回 false。
示例 1:
输入:nums = [1,2,3,1], k = 3, t = 0
输出:true
示例 2:
输入:nums = [1,0,1,1], k = 1, t = 2
输出:true
示例 3:
输入:nums = [1,5,9,1,5,9], k = 2, t = 3
输出:false
提示:
0 <= nums.length <= 2 * 104
-231 <= nums[i] <= 231 - 1
0 <= k <= 104
0 <= t <= 231 - 1
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/7WqeDu
一、桶排序
解题思路:
第一步:
可以利用固定滑动窗口大小来满足abs(i - j) <= k。
在遍历初期不固定滑动窗口,滑动窗口逐渐增大至当 i >=k时,只保留区间 [i-k,i] 的值。
第二步:
可以根据数组中元素的值进行分桶存放,假设桶按位 t 划分,则有:
编号0的桶:存放【0,t】的数
编号1的桶:存放【t+1,2t+1】的数
以此类推。。。
对于负数
编号-1的桶:存放【t-1,-1】的数
编号-2的桶:存放【2t-2,-t-2】的数
可以发现桶ID有:
n>=0 : ID=n/t+1; (例如t=2。区间【0,1,2】内的数除以(t+1)都为0)
n<0 : ID=(n+1)/(t+1) -1
这样我们可以确定一个桶内的元素都符合abs(nums[i] - nums[j]) <= t
定义map存放元素的桶id
①遍历到当前元素时,如果发现桶内已存在元素,则直接返回true
②如果不存在则检查相邻桶内是否存在一个元素使得abs(nums[i] - nums[j]) <= t
因为相邻桶内也可能存在两数差绝对值小于等于t
③当相邻桶内不存在两数差绝对值小于等于t,则把当前桶id存进map,继续遍历下个数
重要一点:
符合一个桶的数值有多个,而hash表只能存放某个桶id的一个数值,
为什么要用hash表存放桶id,是否存在遍历到一个数时把桶内其他元素覆盖?
不会覆盖,因为在滑动窗口的约束下,map中存放的值都是窗口内的值,而当遍历到某个值时,如果当前桶内存有元素,那会直接返回true,所以桶内存放的元素要么没有,要么就只有一个
代码如下:
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
int n = nums.length;
Map<Long,Long> map = new HashMap<>();
long w = (long) t+1;
for(int i=0;i<n;i++){
long id = getId(nums[i],w);
//当前桶存在元素,直接返回true
if(map.containsKey(id)) return true;
//查看相邻桶是否有差值小于等于t
if(map.containsKey(id-1)&&Math.abs(map.get(id-1)-nums[i])<w) return true;
if(map.containsKey(id+1)&&Math.abs(map.get(id+1)-nums[i])<w) return true;
map.put(id,(long)nums[i]);
//约束滑动窗口大小,大小为k,当i逐渐自增大于k时,把 i-k 下标的元素的桶删掉
//为什么是 i>=k? i-k?
//因为如果当前i=k,在当前遍历是ok的,但在for循坏下一轮时就超过,所以提前删除
//i-k一样的道理
if(i>=k){
map.remove(getId(nums[i-k],w));
}
}
return false;
}
public long getId(long x,long w){
if(x>=0) return x/w;
else return (x+1)/w - 1;
}
}