方法二:桶
思路及算法
我们也可以使用利用桶排序的思想解决本题。我们按照元素的大小进行分桶,维护一个滑动窗口内的元素对应的元素。
对于元素 xx,其影响的区间为 [x - t, x + t][x−t,x+t]。于是我们可以设定桶的大小为 t + 1t+1。如果两个元素同属一个桶,那么这两个元素必然符合条件。如果两个元素属于相邻桶,那么我们需要校验这两个元素是否差值不超过 tt。如果两个元素既不属于同一个桶,也不属于相邻桶,那么这两个元素必然不符合条件。
具体地,我们遍历该序列,假设当前遍历到元素 xx,那么我们首先检查 xx 所属于的桶是否已经存在元素,如果存在,那么我们就找到了一对符合条件的元素,否则我们继续检查两个相邻的桶内是否存在符合条件的元素。
实现方面,我们将 \texttt{int}int 范围内的每一个整数 xx 表示为 x = (t + 1) \times a + b(0 \leq b \leq t)x=(t+1)×a+b(0≤b≤t) 的形式,这样 xx 即归属于编号为 aa 的桶。因为一个桶内至多只会有一个元素,所以我们使用哈希表实现即可。
class Solution {
public:
int getID(int x, long w) {
return x < 0 ? (x + 1ll) / w - 1 : x / w;
}
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
unordered_map<int, int> mp;
int n = nums.size();
for (int i = 0; i < n; i++) {
long x = nums[i];
int id = getID(x, t + 1ll);
if (mp.count(id)) {
return true;
}
if (mp.count(id - 1) && abs(x - mp[id - 1]) <= t) {
return true;
}
if (mp.count(id + 1) && abs(x - mp[id + 1]) <= t) {
return true;
}
mp[id] = x;
if (i >= k) {
mp.erase(getID(nums[i - k], t + 1ll));
}
}
return false;
}
};
多解释一句,getID()针对负数时计算桶号处理,看下面例子就明白为啥这样写:(x + 1) / w - 1
getID(-4,3) = -2
getID(-3,3) = -1
getID(-2,3) = -1
getID(-1,3) = -1
用这个例子理解一下,首先x为什么要加一,t=3时,桶0是包含0的,应该包含0、1、2,这些数字可以直接被t整除成0;桶-1应该从-1开始,包含-1、-2、-3,-3被整除的话是-1,因此x+1是让负数整体先右移,方便被整除。 其次为什么最后要减一,同样以上面的例子,桶-1中-1、-2、-3整除完是0,-1是为了让负数桶整体左移一位,这样就不会在桶0处冲突了。