存在重复元素 III
给你一个整数数组 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
提示:
方法一 :滑动窗口 + 有序集合
1、由于abs(i - j) <= k ,可以基于滑动窗口的思想,利用set存储k个元素
2、在添加新元素nums[ i ]之前,用二分找到第一个大于等于nums[ i ] - t 的元素
3、二分查找到的元素 x 可能出现在三个范围之间,
class Solution {
public:
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
int n = nums.size();
set<long> set;
for(int i=0; i<n; i++){
//lower_bound(x)二分查找返回>= x 的最小数字的位置
auto it = set.lower_bound((long)nums[i] - t);
if(it != set.end() && *it - nums[i] <= t){
return true;
}
set.insert(nums[i]);
//达到窗口大小,删除最左侧的元素
if(i >= k){
set.erase(nums[i - k]);
}
}
return false;
}
};
如果用int,为了避免溢出,需要稍微处理,如下:
同样道理
class Solution {
public:
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
int n = nums.size();
set<int> set;
for(int i=0; i<n; i++){
//lower_bound(x)二分查找返回>= x 的最小数字的位置
auto it = set.lower_bound(max(nums[i], INT_MIN + t) - t);
//这部分只需判断*it是否大于nums[i] + t;
if(it != set.end() && *it <= min(nums[i], INT_MAX - t) + t){
return true;
}
set.insert(nums[i]);
if(i >= k){
set.erase(nums[i - k]);
}
}
return false;
}
};
方法二:桶
按照元素的大小进行分桶
由于abs(nums[i] - nums[j]) <= t,所以只要两个数的差值 <= t 即可,
我们可以设定桶的大小为 t + 1。(例如,整数,对于闭区间[a, b], 元素个数为 b - a + 1,这个桶的大小代表一定区间的元素个数)。
官方题解 中的 分桶:当 x >= 0 时,例如 当 t = 9,桶的大小是10, 非负数是0 ~ 9,10~19…这种为一组,自然而然分别被分到0, 1, …桶。
而负数是-1 ~ -10 , -11 ~ -20…这些是一组,按理说应该分别被分到 - 1 , -2, …桶。 而许多语言整除时是向下取整的,如果-1~-10直接除以10,会被分到0 和 - 1两组中,而不是- 1 这一组, 所以先+1变成-0~-9,与正数一致,再除以10,最后减1,
正好是 -1 这一组
class Solution {
public:
//分桶,获得桶id
int getId(long x, long t){
if(x >= 0) {
return x / (t + 1);
}
else{
return (x + 1) / (t + 1) - 1;
}
return 0;
}
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
int n = nums.size();
unordered_map<int, int> map;
for(int i=0; i<n; i++){
long x = nums[i]; //当前元素
int id = getId(x, t);
//位于同一个桶
if(map.count(id)) return true;
//相邻左侧桶
if(map.count(id - 1) && abs(x - map[id - 1]) <= t){
return true;
}
//相邻右侧桶
if(map.count(id + 1) && abs(x - map[id + 1]) <= t){
return true;
}
map[id] = nums[i]; //进桶
if(i >= k){
//删除nums[i - k] 元素所在的桶
map.erase(getId(nums[i - k], t));
}
}
return false;
}
};
(这么秒的桶,赶紧记下来)