滑动窗口10:存在重复元素的三个题

Posted 纵横千里,捭阖四方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了滑动窗口10:存在重复元素的三个题相关的知识,希望对你有一定的参考价值。

滑动窗口的问题还有很多,我们这里先看前面的几道,本文我们分析一个专题:存在重复元素的三道题。对应LeetCode的217,219和220三道题。在做了这三个题之后感觉有点无聊,但是吧,面试算法本来就无聊,还是一起做一做吧。

LeetCode217题 给定一个整数数组,判断是否存在重复元素。如果存在一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。

Leetcode219题,给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。

LeetCode220 给你一个整数数组 nums 和两个整数 k 和 t 。请你判断是否存在 两个不同下标 i 和 j,使得 abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。如果存在则返回 true,不存在返回 false。

 这三个题整体难度不大,前面两个是简单题,最后一个是中等题,我们现在就一起看一下该怎么解决。

1.LeetCode217,给定一个整数数组,判断是否存在重复元素。

如果存在一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。

本题不难,一个方法是先排序,在对数字从小到大排序之后,数组的重复元素一定出现在相邻位置中。因此,我们可以扫描已排序的数组,每次判断相邻的两个元素是否相等,如果相等则说明存在重复的元素。

class Solution {
    public boolean containsDuplicate(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        for (int i = 0; i < n - 1; i++) {
            if (nums[i] == nums[i + 1]) {
                return true;
            }
        }
        return false;
    }
}

排序虽然能解决问题,但是代价是比较高的,我们可以使用Hash来解决,对于数组中每个元素,我们将它插入到哈希表中。如果插入一个元素时发现该元素已经存在于哈希表中,则说明存在重复的元素。

class Solution {
    public boolean containsDuplicate(int[] nums) {
        Set<Integer> set = new HashSet<Integer>();
        for (int x : nums) {
            if (!set.add(x)) {
                return true;
            }
        }
        return false;
    }
}

2. 存在重复元素,无聊的题

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值至多为 k。

示例1:
输入: nums = [1,2,3,1], k = 3
输出: true

示例2:
输入: nums = [1,2,3,1,2,3], k = 2
输出: false

这个题需要维护一个k大小的滑动窗口,用散列表来维护这个k大小的滑动窗口就行了。

(1) 遍历数组,对于每个元素做以下操作:

  • 在散列表中搜索当前元素,如果找到了就返回 true。
  • 在散列表中插入当前元素。
  • 如果当前散列表的大小超过了 kk, 删除散列表中最旧的元素。

(2) 返回 false。

public boolean containsNearbyDuplicate(int[] nums, int k) {
    Set<Integer> set = new HashSet<>();
    for (int i = 0; i < nums.length; ++i) {
        if (set.contains(nums[i])) return true;
        set.add(nums[i]);
        if (set.size() > k) {
            set.remove(nums[i - k]);
        }
    }
    return false;
}

 # 3.存在重复元素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,5,9,1,5,9], k = 2, t = 3
输出:false

用滑动窗口+暴力遍历解法会超时,所以更换TreeSet数据结构。

使用TreeSet维护一个长度小于等于k的滑动窗口。对于一个新加入的元素,我们需要比较滑动窗口中的每一个元素是否都满足题意,即abs(nums[i] - nums[j]) <= t,但若想避开遍历,就要利用好TreeSet的特性,以及它封装的神器函数:ceiling()

  • ceiling()方法用于返回大于或等于給定元素的该集合中的最小元素;如果没有此元素,則返回null。
  • 我们要验证集合中是否有元素位于[myNum[i]-t,myNum[i]+t]区间内,则使用 Long ceiling = set.ceiling(myNum[i]-t);来获取大于等于myNum[i]-t的最小元素,若此元素也小于等于myNum[i]+t,则找到了这样一个元素位于[myNum[i]-t,myNum[i]+t]区间内。

注意维护滑动窗口时的细节

class Solution {
    public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
        int n = nums.length;
        if(n<=1) return false;

        long[] myNum = new long[n];
        for(int i = 0;i<n;i++){
            myNum[i] = nums[i];
        }

        TreeSet<Long> set = new TreeSet<>();
        for(int i = 0;i<n;i++){
            Long ceiling = set.ceiling(myNum[i]-t);
            if(ceiling!=null && ceiling<=myNum[i]+t){
                return true;
            }
            set.add(myNum[i]);
            if(set.size()>k){
                set.remove(myNum[i-k]);
            }
        }
        return false;
    }
}

双指针和滑动窗口主题暂时到这里,接下来我们来研究分治的问题。 

以上是关于滑动窗口10:存在重复元素的三个题的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode219 存在重复元素II 滑动窗口

力扣219. 存在重复元素 II 滑动窗口与哈希解题

面试常考算法题(考察单调队列)--滑动窗口的最大值

281.LeetCode | 220. 存在重复元素 III

题解 | 「力扣」第 413 题:等差数列划分(中等滑动窗口动态规划)

题解 | 「力扣」第 413 题:等差数列划分(中等滑动窗口动态规划)