302,存在重复元素 III

给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最大为 ķ

示例 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

答案:

 1public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
2    if (nums.length < 2 || k == 0) {
3        return false;
4    }
5    TreeSet<Long> set = new TreeSet<>();
6    int i = 0;
7    while (i < nums.length) {
8        Long floor = set.floor((long) nums[i]);
9        Long ceiling = set.ceiling((long) nums[i]);
10        if ((floor != null && nums[i] - floor <= t) ||
11                (ceiling != null && ceiling - nums[i] <= t)) {
12            return true;
13        }
14        set.add((long) nums[i++]);
15        if (i > k) {
16            set.remove((long) nums[i - k - 1]);
17        }
18    }
19    return false;
20}

解析:

首先要读懂题的意思,就是说数组中任意选择两个数,在他们的下标不大于k的情况下,他们的值要小于等于t。我们首先想到的定义一个长度为k的窗口,上面代码set.floor(x)表示的是从set集合中找到小于或等于x的最大值,set.ceiling(x)表示从set集合中找到大于或等于x的最小值,因为set集合中的长度最大是k,所以如果满足条件(第10,11行)直接返回true即可。如果不满足条件,把当前值加入到set集合中,因为集合的长度是k,如果大于k,把最前面的移除掉即可(第16行)。代码比较容易理解,下面再来看一种解法。

 1public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
2    if (k < 1 || t < 0) return false;
3    Map<Long, Long> map = new HashMap<>();
4    for (int i = 0; i < nums.length; i++) {
5        long remappedNum = (long) nums[i] - Integer.MIN_VALUE;
6        long bucket = remappedNum / ((long) t + 1);
7        if (map.containsKey(bucket)
8                || (map.containsKey(bucket - 1) && remappedNum - map.get(bucket - 1) <= t)
9                || (map.containsKey(bucket + 1) && map.get(bucket + 1) - remappedNum <= t))
10            return true;
11        if (map.entrySet().size() >= k) {
12            long lastBucket = ((long) nums[i - k] - Integer.MIN_VALUE) / ((long) t + 1);
13            map.remove(lastBucket);
14        }
15        map.put(bucket, remappedNum);
16    }
17    return false;
18}

这种解法非常巧妙,相比较第一种解法我更欣赏这种解法,因为一般不太容易想到,他的原理和第一种有点类似,也是先定义一个大小为k的窗口,当窗口的值大于k(第11行判断的是等于k先移除,然后在15行在添加)的时候会把最老的给移除掉,他添加的不是当前的值,而是把当前值映射到一个桶里面,这个桶里面所有元素的最大值相差为k,因为如果在这个桶里有重复的元素,说明存在这样的两个数,直接返回true即可(第7行),如果不在一个桶里,在分别判断他的前一个桶和后一个桶值的差距。注意这里的第5行为什么要减去Integer.MIN_VALUE,是为了防止num[i]出现负数,导致映射的桶为负的。其实这种解法还是相当经典的,希望大家能够理解他。

上一篇:servlet请求重定向


下一篇:关于Suricata规则书写的一些总结