参考链接
- https://leetcode-cn.com/problems/contains-duplicate-iii/
- https://leetcode-cn.com/problems/contains-duplicate-iii/solution/cun-zai-zhong-fu-yuan-su-iii-by-leetcode-bbkt/
题目描述
给你一个整数数组 nums 和两个整数 k 和 t 。请你判断是否存在 两个不同下标 i 和 j,使得 abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。
如果存在则返回 true,不存在返回 false。
解题思路
暴力解法
由题意可知,从第一个元素开始,判断当前元素在右边(不需要判断左边,因为随着窗口向右移动,左边大小为k的窗口是之前已经判断过的)大小为k的窗口内是否存在一个数,使得当前元素与其差值小于等于t。即实现一个二层循环,第一层遍历整个数组,第二层遍历窗口。但这种方式是会超时的。
有序集合
既然暴力解法会超时,就想一想暴力解法可以提高效率的地方。可以发现,在第二层循环里,我们直接采用了遍历,这显然是耗时的,可以使用有序集合代替。具体地,集合中会存储当前窗口的元素,先找到(二分查找)大于nums[i]-t的元素,如果同时小于nums[i]+t,那么返回true。
桶
依然是在第二层循环里面优化。将窗口内的元素划分到大小为t+1的桶中,1)如果两个元素存在于一个桶,返回true;2)如果两个元素在相邻桶,且差值小于等于t,返回true。值得注意的是,对负整数划分桶时,采用 x + 1 w − 1 \frac{x+1}{w}-1 wx+1−1。其中 w w w为桶的大小,如 w = 10 w=10 w=10,因为非负数是09,1019…这种一组,而负数是-1~-10, -11-20…这些是一组,如果-1-10直接除以10,会被分到两组中,而不是-1这一组,所以先+1变成-0–9,与正数一致,再除以10,最后减1,正好是-1这一组,其它组也是同理。
排序
这个方法有点没什么道理,但确实能通过。先将数组元素及元素索引存储起来,按照元素大小进行排序,然后再使用两层循环进行遍历。
代码
暴力解法
class Solution {
public:
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
if(nums.size() == 1 || nums.size() == 0)
{
return false;
}
for(int i = 0; i < nums.size() - 1; i ++)
{
for(int j = i + 1; j <= i + k && j < nums.size(); j ++)
{
long res = (long)nums[i] - (long)nums[j];
if(abs(res) <= t)
{
return true;
}
}
}
return false;
}
};
有序集合
class Solution {
public:
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
vector<pair<long, long>> v;
for(int i = 0; i < nums.size(); i ++)
{
v.push_back(make_pair(nums[i], i));
}
sort(v.begin(), v.end());
for(int i = 0; i < v.size(); i ++){
for(int j = i + 1; j < v.size(); j ++){
if(abs(v[i].first - v[j].first) <= t && abs(v[i].second - v[j].second) <= k)
{
return true;
}
if(abs(v[i].first - v[j].first) > t)
{
break;
}
}
}
return false;
}
};
桶
class Solution {
public:
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
int n = nums.size();
set<int> rec;
for (int i = 0; i < n; i++) {
auto iter = rec.lower_bound(max(nums[i], INT_MIN + t) - t);
if (iter != rec.end() && *iter <= min(nums[i], INT_MAX - t) + t) {
return true;
}
rec.insert(nums[i]);
if (i >= k) {
rec.erase(nums[i - k]);
}
}
return false;
}
};
// 写成rec.lower_bound(max(nums[i], INT_MIN + t) - t)是防止溢出。
排序
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;
}
};