leetcode 220. 存在重复元素 III

题目 220. 存在重复元素 III

存在重复元素 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 * 10^4
  • -2^31 <= nums[i] <= 2^31 - 1
  • 0 <= k <= 10^4
  • 0 <= t <= 2^31 - 1

方法一 :滑动窗口 + 有序集合
1、由于abs(i - j) <= k ,可以基于滑动窗口的思想,利用set存储k个元素
2、在添加新元素nums[ i ]之前,用二分找到第一个大于等于nums[ i ] - t 的元素
3、二分查找到的元素 x 可能出现在三个范围之间,

  • nums[ i ] - t < x <= nums[ i ]
  • nums[ i ] < x <= nums[ i ] + t
  • nums[ i ] + t < x
    只要属于[nums[ i ] - t , nums[ i ] + t ]该区间即可
    所以只要 x <= nums[ i ] + t 即可返回 true
class Solution {
public:
    bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
        int n = nums.size();
        set<long> set;
        for(int i=0; i<n; i++){
            //lower_bound(x)二分查找返回>= x 的最小数字的位置
            auto it = set.lower_bound((long)nums[i] - t);
            if(it != set.end() && *it - nums[i] <= t){
                return true;
            }
            set.insert(nums[i]);
            //达到窗口大小,删除最左侧的元素
            if(i >= k){
                set.erase(nums[i - k]);
            }
        }
        return false;
    }
};

如果用int,为了避免溢出,需要稍微处理,如下:

  • max(nums[ i ] , INT_MIN + t) - t ,由于-2^31 <= nums[i] <= 2^31 - 1, 所以nums[ i ] - t 存在溢出风险,假如nums[ i ] - t 下溢出,这步操作可以使下限变为INT_MIN, 也就是 x 的左边界变大 为 INT_MIN

同样道理

  • min(nums[i], INT_MAX - t) + t由于-2^31 <= nums[i] <= 2^31 - 1, 所以nums[ i ] + t 存在溢出风险,假如nums[ i ] + t 上溢出,这步操作可以使上限变为INT_MAX, 也就是 x 的右边界变小 为 INT_MAX
class Solution {
public:
    bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
        int n = nums.size();
        set<int> set;
        for(int i=0; i<n; i++){
            //lower_bound(x)二分查找返回>= x 的最小数字的位置
            auto it = set.lower_bound(max(nums[i], INT_MIN + t) - t);
                               //这部分只需判断*it是否大于nums[i] + t;
            if(it != set.end() && *it <= min(nums[i], INT_MAX - t) + t){
                return true;
            }
            set.insert(nums[i]);
            if(i >= k){
                set.erase(nums[i - k]);
            }
        }
        return false;
    }
};

方法二:桶
按照元素的大小进行分桶
由于abs(nums[i] - nums[j]) <= t,所以只要两个数的差值 <= t 即可,
我们可以设定桶的大小为 t + 1。(例如,整数,对于闭区间[a, b], 元素个数为 b - a + 1,这个桶的大小代表一定区间的元素个数)。

  • 如果两个元素同属一个桶,那么这两个元素必然符合条件
  • 如果两个元素属于相邻桶,那么我们需要校验这两个元素是否差值不超过 t。
  • 如果两个元素既不属于同一个桶,也不属于相邻桶,那么这两个元素必然不符合条件。

官方题解 中的 分桶:当 x >= 0 时,例如 当 t = 9,桶的大小是10, 非负数是0 ~ 9,10~19…这种为一组,自然而然分别被分到0, 1, …桶。
而负数是-1 ~ -10 , -11 ~ -20…这些是一组,按理说应该分别被分到 - 1 , -2, …桶。 而许多语言整除时是向下取整的,如果-1~-10直接除以10,会被分到0 和 - 1两组中,而不是- 1 这一组, 所以先+1变成-0~-9,与正数一致,再除以10,最后减1,
正好是 -1 这一组

class Solution {
public:
    //分桶,获得桶id
    int getId(long x, long t){
        if(x >= 0) {
            return x / (t + 1);
        }
        else{
            return (x + 1) / (t + 1) - 1;
        }
        return 0;
    }

    bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
        int n = nums.size();
        unordered_map<int, int> map;
        for(int i=0; i<n; i++){
            long x = nums[i];   //当前元素
            int id = getId(x, t);
            //位于同一个桶
            if(map.count(id)) return true;
            //相邻左侧桶
            if(map.count(id - 1) && abs(x - map[id - 1]) <= t){
                return true;
            }
            //相邻右侧桶
            if(map.count(id + 1) && abs(x - map[id + 1]) <= t){
                return true;
            }
            map[id] = nums[i];  //进桶
            if(i >= k){
                //删除nums[i - k] 元素所在的桶
                map.erase(getId(nums[i - k], t));
            }
        }
        return false;
    }
};

(这么秒的桶,赶紧记下来)

你可能感兴趣的:(Leetcode)