LeetCode 220. 存在重复元素 III:题解|刷题打卡

137 阅读3分钟

本文正在参与掘金团队号上线活动,点击 查看大厂春招职位

一、题目描述:

力扣今天的每日一题有点意思,虽然是道中等题,但是要考虑的细节还蛮多的。题目链接:LeetCode 220. 存在重复元素 III

image.png

二、思路分析:

看到这种在一个限制长度的区间里面找两个满足条件的题目,很容易就能想到滑动窗口的做法,但是这道题由于数据量巨大,使用简单的滑动窗口来记录肯定会超时,所以就得想办法来优化。

  1. 因为两个元素的下标 iijj 需要满足 abs(ij)kabs(i - j) \leq k,所以我们可以在遍历到元素 xx 的时候只考虑在它前面的 kk 个元素里查找是否存在满足条件的元素,但是一般的数据结构查找的效率较低,我们可以使用有序集合 setset 记录滑动窗口里的元素,再通过二分查找来优化查找的效率。

  2. 虽然数组里可能存在重复元素,但是很容易可以证明只要滑动窗口遇到了重复元素,通过查找就会直接返回 truetrue,所以可以直接使用 setset 而无需 multisetmultiset

  3. 使用有序集合的解法比较直观,建议读者自己尝试,我在这里想讨论的主要是使用桶的做法。前面提到一般的数据结构查找元素的效率较低,有序集合的二分查找复杂度是 O(log(n))O(log(n)),那有没有查找时间更优的数据结构呢?当然有,答案就是桶。

  4. 对于元素 xx 来说,满足条件的元素属于 [xt,x+t][x - t, x + t],这个区间的长度是 2t+12t + 1,如果我们把整个 intint 范围的数都都按照 t+1t + 1 的长度划分到不同的桶里,那么很容易就可以推出:假如 xx 属于 idid 号桶,那么 idid 号桶还有其他元素就说明满足条件,如果 idid 号桶没有,那么我们只需要再判断相邻的两个桶有没有与 xx 的差值小于等于 tt 的元素即可。

  5. 因为只要有一个桶包含超过一个元素就可以返回 truetrue,所以我们可以直接使用哈希表来实现桶。一个需要注意的细节是因为数据范围为 intint 范围,所以在进行加减法的时候需要小心溢出。

  6. 分桶的时候如果直接令桶 id=x/(t+1)id = \lfloor x / (t + 1)\rfloor,会导致 [t+1,t1][-t + 1, t - 1] 都属于第 00 组,不满足按长度 t+1t + 1 分桶的原则,解决的办法是对 x<0x < 0 的情况进行特殊处理让 [t1,1][-t - 1, -1] 的元素归于 1-1 桶,更小的元素按照长度 t+1t + 1 依次划分。

三、AC 代码:

class Solution {
public:
    int getId(int x, long w) {
        // 对负数进行特殊处理可以防止 0 既属于 0 组,也属于 -1 组
        // 从而使每个 ID 所涵盖的元素个数均为 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) && x - mp[id - 1] <= t) {
                return true;
            }
            if (mp.count(id + 1) && mp[id + 1] - x <= t) {
                return true;
            }
            mp[id] = x;
            if (i >= k) {
                mp.erase(getId(nums[i - k], t + 1ll));
            }
        }
        return false;
    }
};

四、总结:

滑动窗口虽然容易,但是题目十分多变,在做这类题型的时候我们需要熟练掌握常用数据结构,以便在实际写题的时候选择适合的数据结构。