[leetcode]220.存在重复元素(3)

给你一个整数数组 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

思路:桶

遍历数组,对于任意的数nums[i],可以利用nums[i]/(t+1)分到不同的桶中,这些桶的数字分别是[0,t],[t+1,2t]…[nt+1,(n+1)t]。

如图所示
[leetcode]220.存在重复元素(3)

如此分好桶之后,桶中的元素可以满足一下几个条件:
1.同一个桶之内的元素绝对满足abs(nums[i] - nums[j]) <= t
2.相邻的桶内有可能出现元素满足abs(nums[i] - nums[j]) <= t
3.不相邻的桶一定不会出现abs(nums[i] - nums[j]) <= t,因为不相邻的桶之内的元素差值最小为t+1

于是可以有伪代码:

for(num : nums){
	size = t + 1;
	计算num所在的桶的编号idx;
	if(存在该桶) return true;
	if(不存在该桶),检查相邻的桶,如果相邻的桶中存在范围在[num - t,num + t]的数,return true;
	建桶;
	删除下标范围不在[max(0,i-k), i]内的桶
}
return false;

ps:

1.size = t + 1的原因

目的是为了确保差值小于等于 t 的数能够落到一个桶中。

假设 [0,1,2,3],t = 3,显然四个数都应该落在同一个桶。

如果不对 t 进行 +1 操作的话,那么 [0,1,2] 和 [3] 会被落到不同的桶中,那么为了解决这种错误,我们需要对 t 进行 +1 作为 size 。

这样我们的数轴就能被分割成:

0 1 2 3 | 4 5 6 7 | 8 9 10 11 | 12 13 14 15 | …

总结一下,令 size = t + 1 的本质是因为差值为 t 两个数在数轴上相隔距离为 t + 1,它们需要被落到同一个桶中。

当明确了 size 的大小之后,对于正数部分我们则有 idx = nums[i] / size。

2. 负数逻辑

由于我们处理正数的时候,处理了数值 0,因此我们负数部分是从 -1 开始的。

还是我们上述 例子,此时我们有 t = 3 和 size = t + 1 = 4。

考虑 [-4,-3,-2,-1] 的情况,它们应该落在一个桶中。

如果直接复用 idx = nums[i] / size 的话,[-4] 和 [-3,-2,-1] 会被分到不同的桶中。

根本原因是我们处理整数的时候,已经分掉了数值 0。

这时候我们需要先对 nums[i] 进行 +1 操作(即将负数部分在数轴上进行整体右移),即得到 (nums[i] + 1) / size。

这样一来负数部分与正数部分一样,可以被正常分割了。

但由于 0 号桶已经被使用了,我们还需要在此基础上进行 -1,相当于将负数部分的桶下标(idx)往左移,即得到 ((nums[i] + 1) / size) - 1。

AC代码:

class Solution {
   public:
    long size;
    bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
        int n = nums.size();
        map<long, long> m;
        size = t + 1L;
        for (int i = 0; i < n; i++) {
            long u = nums[i] * 1L;
            long idx = getIdx(u);
            // 目标桶已存在(桶不为空),说明前面已有 [u - t, u + t] 范围的数字
            if (m.find(idx) != m.end()) return true;
            // 检查相邻的桶
            long l = idx - 1, r = idx + 1;
            if (m.find(l) != m.end() && abs(u - m[l]) <= t) return true;
            if (m.find(r) != m.end() && abs(u - m[r]) <= t) return true;
            // 建立目标桶
            m.insert({idx, u});
            // 移除下标范围不在 [max(0, i - k), i) 内的桶
            if (i >= k) m.erase(getIdx(nums[i - k]));
        }
        return false;
    }
    long getIdx(long u) {
        return u >= 0 ? u / size : (u + 1) / size - 1;
    }
};
上一篇:火线和地线之间的电压是220伏,那要零线干嘛?


下一篇:leetcode 220. Contains Duplicate III | 220. 存在重复元素 III (Treeset解法+分桶解法)