题目
给你一个整数数组 nums 和两个整数 k 和 t 。请你判断是否存在 两个不同下标 i 和 j,使得 abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。
如果存在则返回 true,不存在返回 false。
提示:
0 <= nums.length <= 2 * 10^4
-2^31 <= nums[i] <= 2^31 - 1
0 <= k <= 10^4
0 <= t <= 2^31 - 1
思路
题目很简单,在数组nums内,对于索引值i找到i-k到i+k中符合abs(nums[i] - nums[j]) <= t条件的值。
对于这种具有固定子数组范围,可以采用滑动窗口。
当i在窗口最右边,i后面可以有k个数(到i-k为止)
当i滑到窗口最左边,i前面可以有k个数(到i+k为止)
因此滑动窗口大小只需要k即可
当窗口沿着数组从左到右滑动时,窗口最前面添加数组当前索引内容,并丢掉窗口内最后一个数,保持窗口大小保持k不变。
但是滑动时,对于窗口前的数x,都需要将它与窗口内其他数对比,使abs(nums[i] - nums[j]) <= t。窗口到达nums[i]时,nums[i]只需要找比自己小t之内的数。因为窗口更新后新数值nums[j]会和nums[i]对比是不是比nums[j]小t,这就相当于nums[i]找比自己大t之内的数了。
这时就遇到了问题:nums[i]需要在窗口内找比自己小t的数,如果采用遍历一遍窗口值进行对比,最后会出现超时(自身尝试过)
因此就不能采用再叠加一次循环。
这时可以采用有序集合set。我们可以将窗口中数组存入set,并在遍历数组时进行更新。
存入set后,数值会进行排序,有利于查找。set排序采用红黑树,时间复杂度为O(logn),,不会出现超时现象。
同时 set有一个lower_bound(val)函数,时间复杂度为O(logn) 可以返回第一个大于或等于 val 的元素的双向迭代器。这正符合我们查找值的需要。
对于窗口最前端nums[i],通过lower_bound(nums[i]-t)
查找第一个大于或等于nums[i]-t的值,如果值存在并且小于nums[i]+t,说明为true。
同时需要注意nums[i]数值在int范围内,nums[i]-t和nums[i]+t可能会溢出,需要进行判断。或者转化为long long数据类型
代码
class Solution {
public:
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
int len = nums.size();
set<int> st;
for(int i=0;i<len;i++){
auto iter = st.lower_bound(max(nums[i], INT_MIN+t)-t);
// max(nums[i], INT_MIN+t)-t是进行溢出判断,如果nums[i]-t小于INT_MIN,则将其赋值为INT_MIN
if(iter!=st.end() && *iter<=min(nums[i], INT_MAX-t)+t)
// 判断是否存在大于或等于nums[i]-t的值,并且要小于nums[i]+t
return true;
st.emplace(nums[i]);
if(k<=i) //窗口大小够k个了,排掉最后一个
st.erase(nums[i-k]);
}
return false;
}
}