220. 存在重复元素 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
提示:
0 <= nums.length <= 2 * 104
-231 <= nums[i] <= 231 - 1
0 <= k <= 104
0 <= t <= 231 - 1
暴力解肯定是会超时的
思路
像这种找两个下标的,一般情况下 ,第一步都是利用给出的条件把它化成查找一个下标的形式,要满足abs(i - j) <= k,这里有两种思路,一个是哈希表,一个是红黑树,为什么要用这两种呢?可以从一开始像,如果是暴力解法,要做的就是对每一个元素查找周围的2k+1个元素,也就是O(nk),这里两种方法都遵循一个思路,就是减少k,也缩短下面的过程:
给定下标i,在[i-k,i+k]中判断是否存在[nums[i]-t,nums[i]+t]的值
红黑树
维护一个红黑树,包含i之前的k个元素(砍掉一半,后面的由后面的元素来判断),查找第一个大于等于nums[i]-t的元素是不是小于nums[i]+t就行了,因为红黑树每次查找的时间复杂度在O(lgn)所以总的时间复杂度为O(nlgn).
class Solution {
public:
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
set<long> rbtree;
for(int i=0;i<nums.size();i++)
{
auto first_x_sub_t = rbtree.lower_bound(long(nums[i])-t);
if(first_x_sub_t!=rbtree.end() && *first_x_sub_t <= long(nums[i])+t)
{
//cout<<*first_x_sub_t<<' '<<nums[i]<<' '<<i<<endl;
return true;
}
rbtree.insert(nums[i]);
if(i>=k)
rbtree.erase(nums[i-k]);
}
return false;
}
};
哈希表
维护一个哈希表,包含i之前的k个元素。
利用了桶排序的思想,设置桶的大小为t+1,这样每次查找元素,只要找当前所应归属的桶和周围两个就行了,对每一个元素,都在哈希表中查找当前桶和周围的两个,时间复杂度为O(1),总的时间复杂度为O(n)。
class Solution {
public:
long get_id(long x,long size)
{
return x>=0?x/size:(x+1)/size-1;
}
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
unordered_map<long,long> mp;
for(int i=0;i<nums.size();i++)
{
long x = nums[i];
long id = get_id(x,long(t)+1ll);
if(mp.count(id) == true)
return true;
if(mp.count(id-1) == true && abs(mp[id-1]-x) <= t)
return true;
if(mp.count(id+1) == true && abs(mp[id+1]-x) <= t)
return true;
mp[id]=x;
if(i >= k)
mp.erase(get_id(nums[i - k], t + 1ll));
}
return false;
}
};
总结
主要学习到的是对数据结构的解构和应用,像是红黑树和哈希表,都是非常高效的查找元素的方法,可以将问题解构吗,然后使用特定的数据结构去优化它。c++中对应这两者的是set和unordered_map,这类题思路可能很直接,但是,要在如何将问题转化到特定的数据结构上比较花心思。