思路:滑窗 +有序集合 O(nlogn)
具体思路:维护一个长度为k+1,abs(nums[i] - nums[j]) <= t的窗口。
如何判断在集合中存在[nums[i] - t, nums[i] + t]的元素呢?
找到大于等于nums[i] - t的最小元素,如果该元素小于等于 nums[i] + t,则为true
class Solution {
public:
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, long long t) {
int n = nums.size(), l = 0;
set<long long> st; //
for (int r = 0; r < n; ++r) {
if(r >= k + 1) st.erase(nums[l++]); //
auto it = st.lower_bound(nums[r] - t);
if (it != st.end() && *it <= nums[r] + t) return true;
st.insert(nums[r]);
}
return false;
}
};
易错点:
1:abs(i - j) <= k是长度为k+1的滑窗,不是长度为k的滑窗。
2:固定滑窗应该先删除左端,再添加右端,因为当右端和左端相等时,就把右端删掉了。
思路二(优化了思路一) O(n)
思路一主要耗时在查找窗口内是否存在[nums[i] - t, nums[i] + t]的元素,可以用分桶的思想优化。
每个桶放t + 1的元素,nums[i] / (t + 1) 得到桶的编号,假设t = 3,除以t+1 = 4。
因此桶的编号与对应元素的对应关系为:
1:[4,7]
0:[-3, 3]
-1:[-4,-7]
发现0号桶超过了t + 1个元素。
怎么解决呢?当nums[i]为负数时,桶编号计算公式为:(nums[i] + 1) / (t + 1) - 1。
这样的话:
1:[4,7]
0:[0, 3]
-1:[-4,-1]
成功解决了这个问题。
因此桶编号计算公式为:
nums[i]大于等于0时,buget = nums[i] / (t + 1) 。
否则:buget = (nums[i] + 1) / (t + 1) - 1
在一个桶中,return true,否则看一下隔壁桶中的元素是否与该元素的距离小于等于t(中间间隔有桶的距离一定大于t + 1)。
class Solution {
public:
unordered_map<long long, long long> hash;//桶编号->val
long long getindex(int n, int t) {
return n >= 0 ? n / (t + 1ll) : (n + 1) / (t + 1ll) - 1; //
}
bool judge(int num, int t) {
long long buget = getindex(num, t);
if (hash.count(buget)) return true;
if (hash.count(buget - 1) && abs(hash[buget - 1] - num) <= t) return true;
if (hash.count(buget + 1) && abs(hash[buget + 1] - num) <= t) return true; //
return false;
}
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {// long long
int n = nums.size(), l = 0;
for (int r = 0; r < n; ++r) {
if (r >= k + 1) hash.erase(getindex(nums[l++], t));
if (judge(nums[r], t)) return true;
hash[getindex(nums[r], t)] = nums[r];
}
return false;
}
};
易错点:
1:计算buget时,t+1可能会越界int-> t + 1ll
hash.count(buget - 1)可能会越界int->hash键为long long
abs(hash[buget + 1] - num)可能会越界int->hash值为long long