「力扣」第 220 题:存在重复元素 III


「力扣」第 220 题:存在重复元素 III

传送门:220. 存在重复元素 III

题解地址:滑动窗口 + 二叉搜索树找上下边界(Python 代码、Java 代码)

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

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

01.png

这道题得先读懂题目的意思:存在性问题,找到符合题意的“数据对”即可返回 true,所有可能的情况都看完以后没有找到才返回 false

题目意思翻译一下:在数组 nums[i] 中,在任意有效区间 [i, i + k] 里是否存在两个数的绝对值小于等于 t,存在则返回 true,不存在返回 false

方法一:暴力解法

枚举所有长度小于等于 k + 1 的“下标对()”,只要发现 nums[i] - nums[j] 的绝对值小于 t ,就返回 true

参考代码 1

public class Solution {

    public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
        int len = nums.length;
        long a;
        long b;

        for (int i = 0; i < len; i++) {
            for (int j = i + 1; j < len && j <= i + k; j++) {
                a = nums[i];
                b = nums[j];
                if (Math.abs(a - b) <= t) {
                    return true;
                }
            }
        }
        return false;
    }
}

复杂度分析

  • 时间复杂度:$O(N^2)$,这里数组的长度为 $N$,枚举可能的数对 $(i, j)$ 。
  • 空间复杂度:$O(1)$。

很容易想到的优化的思路是:以空间换时间,这里的空间还需要有比较大小的功能,比较容易想到的数据结构是二叉搜索树。

方法二:滑动窗口(以空间换时间)

固定长度的滑动窗口问题。和 3、76、209 还不太一样。

题目意思翻译一下:在数组 nums[i] 中,在任意有效区间 [i, i + k] 里是否存在两个数的绝对值小于等于 t,即

$$
|nums[i] - nums[j] | <= t
$$

等价于 $nums[i] - nums[j] <= t$ 并且 $nums[i] - nums[j] >= -t$,

整理得:$nums[i] <= t + nums[j]$ 并且 $nums[i] >= nums[j] - t$。

于是算法可以在遍历的时候,把遍历到的数存到 BST 中,边遍历边查找。

根据刚才得到式子 $nums[i] >= nums[j] - t$,假设当前遍历到的数是 $nums[j]$,$nums[i]$ 就是 $nums[j] - t$ 的天花板数(ceiling),这个数同时还要小于等于 $t + nums[j]$。根据这一点写滑动窗口的代码。

  • ceiling(key) 函数:返回大于等于 key 的最小元素,如果不存在,返回空。下面的是这个函数的文档(通过 Intellij IDEA 查看)。
/**
  * Returns the least key greater than or equal to the given key,
  * or {@code null} if there is no such key.
  *
  * @param key the key
  * @return the least key greater than or equal to {@code key},
  *         or {@code null} if there is no such key
  * @throws ClassCastException if the specified key cannot be compared
  *         with the keys currently in the map
  * @throws NullPointerException if the specified key is null
  *         and this map does not permit null keys
  */
K ceilingKey(K key);

参考代码 2

import java.util.TreeSet;

public class Solution {

    public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
        // 滑动窗口结合查找表,此时滑动窗口即为查找表本身(控制查找表的大小即可控制窗口大小)
        TreeSet set = new TreeSet<>();
        for (int i = 0; i < nums.length; i++) {
            // 边添加边查找
            // 查找表中是否有大于等于 nums[i] - t 且小于等于 nums[i] + t 的值
            Long ceiling = set.ceiling((long) nums[i] - (long) t);
            if (ceiling != null && ceiling <= ((long) nums[i] + (long) t)) {
                return true;
            }
            // 添加后,控制查找表(窗口)大小,移除窗口最左边元素
            set.add((long) nums[i]);
            if (set.size() == k + 1) {
                set.remove((long) nums[i - k]);
            }
        }
        return false;
    }
}

复杂度分析

  • 时间复杂度:$O(N\log K)$,遍历数组使用 $O(N)$,在遍历的同时向二叉搜索树中插入元素和移除元素的时间复杂度是 $O(\log K)$。
  • 空间复杂度:$O(1)$。

另一种写法:在一开始就把滑动窗口左边的元素删除。

参考代码 3

import java.util.TreeSet;

public class Solution11 {

    public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
        int len = nums.length;
        // 特判
        if (len == 0 || k <= 0 || t < 0) {
            return false;
        }

        TreeSet set = new TreeSet<>();

        for (int i = 0; i < len; i++) {
            if (i > k) {
                set.remove((long) nums[i - k - 1]);
            }

            Long ceiling = set.ceiling((long) nums[i] - (long) t);
            if (ceiling != null && ceiling <= (long) nums[i] + (long) t) {
                return true;
            }

            set.add((long) nums[i]);
        }
        return false;
    }
}

复杂度分析:(同上)


文章作者: liweiwei1419
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 liweiwei1419 !
评论
 上一篇
「力扣」第 239 题:滑动窗口的最大值 「力扣」第 239 题:滑动窗口的最大值
「力扣」第 239 题:滑动窗口的最大值题解地址:最大索引堆 + 双端队列存索引值的思路分析(Python 代码、Java 代码)。 说明:文本首发在力扣的题解版块,更新也会在第 1 时间在上面的网站中更新,这篇文章只是上面的文章的一个快照
下一篇 
「力扣」第 209 题:长度最小的子数组(中等) 「力扣」第 209 题:长度最小的子数组(中等)
「力扣」第 209 题:长度最小的子数组(中等) 中文网址:209. 长度最小的子数组 ; 英文网址:209. Minimum Size Subarray Sum 。 给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中
  目录