LeetCode 220. 存在重复元素 III
一道medium题,但是不好想,想不到。
题目描述
给你一个整数数组 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
解题思路
首先,直接暴力查找每个元素前后k个元素进行比较,暴力解法的时间复杂度是 O(nk)。超过这个复杂度的肯定不行。
看到前后k个元素,第一时间想到了滑动窗口、单调队列,但是不对。单调队列给出的是最值,我们在这里想找的是区间里离当前数最近的值。
每次都给窗口里k个数排序的话,时间复杂度 O(nklogk) 显然比暴力还慢。有没有办法每次增减元素,不需要全部重新排序一遍的?
有,std::set 本身有序,插入删除只需要 O(logk) 时间,并且还支持 set<T>:::lower_bound(T)
set<T>::upper_bound(T)
这种 O(logk) 的查询操作,这就是我们的所求了。
参考代码
注意int类型溢出的问题。
/*
* @lc app=leetcode id=220 lang=cpp
*
* [220] Contains Duplicate III
*/
// @lc code=start
class Solution {
public:
/*
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
if (k == 0) return false;
deque<pair<int,int>> mins; // <index, value>
deque<pair<int,int>> maxs; // <index, value>
int i = 0;
for (; i <= k; i++) {
if (nums.size() <= i) break; // guard
while (!mins.empty() && mins.back().second > nums[i]) {
mins.pop_back();
}
mins.emplace_back(i, nums[i]);
if (i != mins.front().first
&& nums[i] - mins.front().second <= t) {
cout << i << nums[i] << endl;
return true;
}
while (!maxs.empty() && maxs.back().second < nums[i]) {
maxs.pop_back();
}
maxs.emplace_back(i, nums[i]);
if (i != maxs.front().first
&& maxs.front().second - nums[i] <= t) {
cout << i << nums[i] << endl;
return true;
}
}
for (; i<nums.size(); i++) {
if (mins.front().first + k < i) {
mins.pop_front(); // 过期
}
while (!mins.empty() && mins.back().second > nums[i]) {
mins.pop_back(); // 淘汰
}
mins.emplace_back(i, nums[i]);
if (i != mins.front().first
&& nums[i] - mins.front().second <= t) {
cout << i << nums[i] << endl;
return true;
}
if (maxs.front().first + k < i) {
maxs.pop_front(); // 过期
}
while (!maxs.empty() && maxs.back().second < nums[i]) {
maxs.pop_back(); // 淘汰
}
maxs.emplace_back(i, nums[i]);
if (i != maxs.front().first
&& maxs.front().second - nums[i] <= t) {
cout << i << nums[i] << endl;
return true;
}
}
return false;
} // WA,滑动窗口是不对的,因为会出现一个合理答案刚出来就被新的最值挤下去的情况,其实我们需要比较的不是一个数和窗口里最值的差值,而是它和窗口里最近原始的差值,这才是问题关键。
// -> 思路来了,lower_bound / upper_bound
*/
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
if (k == 0) return false;
// set 不用担心重复元素,有重复元素一定return true了
set<int64_t> st; // nums[i] - t 会溢出 int 范围
for (int i=0; i<nums.size(); i++) {
if (i >= k+1) {
st.erase(nums[i-k-1]);
}
auto lb = st.lower_bound(nums[i]); // 找比x大的
if (lb != st.end() && *lb - nums[i] <= t) {
cout << *lb << ' ' << i << endl;
return true;
}
auto lb2 = st.lower_bound(int64_t(nums[i]) - t); // 找比x小的
if (lb2 != st.end() && llabs(*lb2 - nums[i]) <= t) {
cout << *lb2 << ' ' << i << endl;
return true;
}
st.insert(nums[i]);
}
return false;
}
/*
[2147483640,2147483641]
1
100
expected: true
[]
0
0
expected: false
[-2147483640,-2147483641]
1
100
expected: true
// int 类型溢出
*/
};
// @lc code=end