219 Contains Duplicate II

本文介绍了一种利用滑动窗口和哈希表的数据结构来解决数组中寻找相隔不超过k个位置的重复元素的问题。通过两种不同的解法思路,一种是元素追着窗口跑,另一种是元素领着窗口跑,实现了O(N)时间复杂度的高效解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

Given an array of integers and an integer k, find out whether there are two distinct indices i and j in the array such that nums[i] = nums[j] and the absolute difference between i and j is at most k.

Example 1:

Input: nums = [1,2,3,1], k = 3
Output: true

Example 2:

Input: nums = [1,0,1,1], k = 1
Output: true

Example 3:

Input: nums = [1,2,3,1,2,3], k = 2
Output: false


解法思路(一)

  • 外层循环游标 i[0, nums.length - 1) 间遍历,内层循环在 [i + 1, k] 间遍历,暴力解法,当然可以,只不过时间复杂度是 O(N*K);
O(N) 时间复杂度的解法
  • 在遍历开始前,把滑动窗口中 k 个元素先存入一个查找表,由于窗口中的元素有可能是重复的,所以使用 HashMap 统计窗口中每个元素出现的频次;
  • 当游标 i 在遍历 [0, nums.length - 1) 的时候,只需要 O(1) 的时间就可以在 nums[i] 对应的窗口中判断出是否有与其相等的元素;

解法实现(一)

时间复杂度
  • O(N);
空间复杂度
  • O(K);
关键词

滑动窗口 哈希表 HashMap 元素可能重复的查询表 窗口滑动的边界判断

实现细节
  • k >= nums.length 这个边界的判断,nums.length 指向数组最后一个元素的后面,是数组外的位置,如果 k 的范围超过数组的边界,那么滑动窗口就缩小为最长到数组的边界 nums.length - 1
  • 至于 k 的精确语义,根据题目中给的例子可以确定;
package leetcode._219;

import java.util.HashMap;

public class Solution219_1 {

    public boolean containsNearbyDuplicate(int[] nums, int k) {

        HashMap<Integer, Integer> windowK = new HashMap();

        for (int i = 1; i <= (k >= nums.length ? nums.length - 1 : k); i++) {
            if (windowK.containsKey(nums[i])) {
                windowK.put(nums[i], windowK.get(nums[i]) + 1);
            } else {
                windowK.put(nums[i], 1);
            }
        }

        for (int i = 0; i < nums.length - 1; i++) {
            if (windowK.containsKey(nums[i])) {
                return true;
            } else if(i + k + 1 < nums.length){
                if (windowK.containsKey(nums[i + k + 1])) {
                    windowK.put(nums[i + k + 1], windowK.get(nums[i + k + 1]) + 1);
                } else {
                    windowK.put(nums[i + k + 1], 1);
                }
            }
            if (windowK.containsKey(nums[i+1])) {
                windowK.put(nums[i + 1], windowK.get(nums[i + 1]) - 1);
                if (windowK.get(nums[i + 1]) == 0) {
                    windowK.remove(nums[i + 1]);
                }
            }
        }

        return false;
    }

    public static void main(String[] args) {
//        int[] arr = {1, 2, 3, 1}; int k = 3;
//        int[] arr = {1, 0, 1, 1}; int k = 1;
//        int[] arr = {1, 2, 3, 1, 2, 3}; int k = 2;
        int[] arr = {1}; int k = 1;
//        int[] arr = {99, 99}; int k = 2;
        boolean result = (new Solution219_1()).containsNearbyDuplicate(arr, k);
        System.out.println(result);
    }

}

解法思路(二)

  • 解法思路(一)是“找自己”的元素“追着”窗口跑,解法思路(二)是“找自己”的元素“领着”窗口跑;

解法实现(二)

时间复杂度
  • O(n);
空间复杂度
  • O(k);
关键字

哈希表 HashSet

k 的几何意义
  • “找自己”的元素最多在距离自己第 k 个的位置“找到自己”;
  • 滑动窗口的长度是 k
关于滑动窗口
  • 滑动窗口的长度是 k
  • 滑动窗口的起点是“找自己”的后一个元素;
  • 滑动窗口的终点是距“找自己”第 k 远的元素;
  • 如果滑动窗口被撑到最长,也没能在其中找到“自己”,那么就要缩短滑动窗口,让距“找自己”第 k 个元素“出离”滑动窗口,并将“找自己”的元素纳入滑动窗口,与滑动窗口右邻接的元素成为新的“找自己”的元素;
关于滑动窗口中的元素为什么可以用 HashSet 盛装
  • 滑动窗口的长度最大为 k,滑动窗口被撑到最大只有当滑动窗口中没有相等的元素,如果滑动窗口在没有被撑到最大的时候,出现了一个滑动窗口中有的元素想进入滑动窗口,那它是进不去的,因为它已经找到了“自己”,算法运行结束;
package leetcode._219;

import java.util.HashSet;

// 219. Contains Duplicate II
// https://leetcode.com/problems/contains-duplicate-ii/description/
// 时间复杂度: O(n)
// 空间复杂度: O(k)
public class Solution219_2 {

    public boolean containsNearbyDuplicate(int[] nums, int k) {

        if(nums == null || nums.length <= 1)
            return false;

        if(k <= 0)
            return false;

        HashSet<Integer> record = new HashSet<Integer>();
        for(int i = 0 ; i < nums.length; i ++){
            if(record.contains(nums[i]))
                return true;

            record.add(nums[i]);
            if(record.size() == k + 1)
                record.remove(nums[i-k]);
        }

        return false;
    }

    private static void printBool(boolean b){
        System.out.println(b ? "True" : "False");
    }

    public static void main(String[] args) {

        int[] nums = {1, 2, 1};
        int k = 1;
        printBool((new Solution219_2()).containsNearbyDuplicate(nums, k));
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值