[LeetCode 220.] 存在重复元素 III

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
posted @ 2021-09-15 23:03  与MPI做斗争  阅读(31)  评论(0编辑  收藏  举报