LeetCode 219. 存在重复元素 II(滑动窗口 + 哈希表详解)

LeetCode 219. 存在重复元素 II(滑动窗口 + 哈希表详解)

题目链接

219. 存在重复元素 II - 力扣(LeetCode)


题目描述

给你一个整数数组 nums 和一个整数 k,判断数组中是否存在两个 不同的索引 ij,满足 nums[i] == nums[j]abs(i - j) <= k。如果存在,返回 true;否则,返回 false


示例

示例 1

text 复制代码
输入:nums = [1,2,3,1], k = 3
输出:true
解释:nums[0] = 1, nums[3] = 1,abs(0 - 3) = 3 <= 3

示例 2

text 复制代码
输入:nums = [1,0,1,1], k = 1
输出:true
解释:nums[2] = 1, nums[3] = 1,abs(2 - 3) = 1 <= 1

示例 3

text 复制代码
输入:nums = [1,2,3,1,2,3], k = 2
输出:false
解释:任意两个相同元素的索引差都大于 2

提示

  • 1 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9
  • 0 <= k <= 10^5

解题思路

这道题是 LeetCode 217 的进阶版本,增加了 索引距离限制 。我们需要判断是否存在两个相同元素,且它们的索引差不超过 k

核心思想:滑动窗口 + 哈希集合

维护一个大小为 k 的滑动窗口,窗口内存储最近 k 个元素:

  1. 遍历数组,对于当前元素 nums[i]
    • 检查它是否已经在窗口(哈希集合)中。
    • 如果在窗口中 ,说明找到了两个相同元素,且索引差不超过 k,返回 true
    • 如果不在窗口中,将其添加到窗口中。
  2. 维护窗口大小:当窗口大小超过 k 时,移除最旧的元素(nums[i - k])。
  3. 遍历结束后,返回 false

为什么使用滑动窗口?

因为题目要求 abs(i - j) <= k,这意味着两个相同元素的索引差不能超过 k。滑动窗口正好可以维护最近 k 个元素:

  • 窗口大小始终不超过 k
  • 窗口内的元素与当前元素的索引差都不超过 k
  • 如果当前元素在窗口内,说明找到了满足条件的重复元素。

算法步骤

  1. 创建一个空的哈希集合 window
  2. 遍历数组,对于索引 i
    • 如果 nums[i]window 中,返回 true
    • nums[i] 添加到 window 中。
    • 如果 window.size() > k,移除 nums[i - k]
  3. 遍历结束后,返回 false

举例说明

例子 1:nums = 1,2,3,1, k = 3

步骤 i numsi 窗口状态 操作
初始 - - {} -
1 0 1 {1} 1 不在窗口中,添加
2 1 2 {1,2} 2 不在窗口中,添加
3 2 3 {1,2,3} 3 不在窗口中,添加
4 3 1 {1,2,3} 1 在窗口中,返回 true

最终返回 true

例子 2:nums = 1,2,3,1,2,3, k = 2

步骤 i numsi 窗口状态 操作
初始 - - {} -
1 0 1 {1} 1 不在窗口中,添加
2 1 2 {1,2} 2 不在窗口中,添加
3 2 3 {2,3} 窗口超限,移除 1,添加 3
4 3 1 {3,1} 窗口超限,移除 2,添加 1
5 4 2 {1,2} 窗口超限,移除 3,添加 2
6 5 3 {2,3} 窗口超限,移除 1,添加 3

遍历结束,没有发现重复元素,返回 false


代码实现

Java 版本

java 复制代码
import java.util.HashSet;

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

Python 版本

python 复制代码
class Solution(object):
    def containsNearbyDuplicate(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: bool
        """
        window = set()
        for i in range(len(nums)):
            if nums[i] in window:
                return True
            window.add(nums[i])
            # 窗口超过k,移除滑出去的旧元素
            if len(window) > k:
                window.remove(nums[i - k])
        return False

代码详解

1. 创建滑动窗口

Java:

java 复制代码
HashSet<Integer> window = new HashSet<>();

Python:

python 复制代码
window = set()

创建一个哈希集合作为滑动窗口。

2. 遍历数组

Java:

java 复制代码
for(int i = 0; i < nums.length; i++){

Python:

python 复制代码
for i in range(len(nums)):

遍历数组中的每个元素。

3. 检查元素是否在窗口中

Java:

java 复制代码
if(window.contains(nums[i])){
    return true;
}

Python:

python 复制代码
if nums[i] in window:
    return True

如果当前元素已经在窗口中,说明找到了满足条件的重复元素。

4. 添加元素到窗口

Java:

java 复制代码
window.add(nums[i]);

Python:

python 复制代码
window.add(nums[i])

将当前元素添加到窗口中。

5. 维护窗口大小

Java:

java 复制代码
if(window.size() > k){
    window.remove(nums[i - k]);
}

Python:

python 复制代码
if len(window) > k:
    window.remove(nums[i - k])

当窗口大小超过 k 时,移除最旧的元素。

关键点:

  • 窗口大小始终保持不超过 k
  • 移除的元素是 nums[i - k],即当前索引减去 k 的位置。

复杂度分析

  • 时间复杂度:O(n),其中 n 是数组的长度。只需要遍历一次数组,哈希集合的查找、插入和删除操作都是 O(1)。
  • 空间复杂度:O(min(n, k)),窗口大小最多为 min(n, k)。

其他解法:哈希表存储索引

思路

使用哈希表存储每个元素最近出现的索引 。遍历数组时,检查当前元素是否已经在哈希表中,且索引差不超过 k

Java 代码

java 复制代码
import java.util.HashMap;
import java.util.Map;

class Solution {
    public boolean containsNearbyDuplicate(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for(int i = 0; i < nums.length; i++){
            if(map.containsKey(nums[i]) && i - map.get(nums[i]) <= k){
                return true;
            }
            map.put(nums[i], i);
        }
        return false;
    }
}

复杂度分析

  • 时间复杂度:O(n),需要遍历数组。
  • 空间复杂度:O(n),需要存储所有元素的索引。

优缺点对比

方法 时间复杂度 空间复杂度 优缺点
滑动窗口 O(n) O(min(n,k)) 空间更优,推荐使用
哈希表存索引 O(n) O(n) 思路更直观,但空间较大

常见问题解答

Q1:为什么窗口大小超过 k 时要移除 numsi - k

因为窗口需要保持大小为 k。当索引为 i 时,窗口应该包含索引 i-k+1i 的元素(共 k 个)。因此,当添加新元素后,如果窗口大小超过 k,需要移除索引 i - k 的元素。

Q2:这道题和 LeetCode 217 有什么区别?

  • 217 题:只需要判断是否存在重复元素,没有索引距离限制。
  • 219 题 :需要判断是否存在重复元素,且索引差不超过 k

219 题增加了索引距离的限制,需要使用滑动窗口来维护最近 k 个元素。

Q3:如果 k 大于数组长度怎么办?

如果 k >= nums.length,窗口大小永远不会超过 k,相当于判断整个数组是否存在重复元素,退化为 217 题


总结

这道题是滑动窗口 + 哈希表的经典应用,核心要点:

  1. 滑动窗口 :维护最近 k 个元素的集合。
  2. 哈希集合:快速判断元素是否在窗口中。
  3. 窗口维护 :当窗口大小超过 k 时,移除最旧的元素。

关键点回顾

  • 使用哈希集合作为滑动窗口
  • 检查当前元素是否在窗口中
  • 维护窗口大小不超过 k
  • 时间复杂度 O(n),空间复杂度 O(min(n,k))

滑动窗口是解决数组和字符串问题的常用技巧,掌握后可以解决很多相关问题。


如果觉得本文对你有帮助,欢迎点赞、收藏、关注,我会持续更新更多 LeetCode 题解和算法干货!

相关推荐
吞下星星的少年·-·23 分钟前
The 2025 ICPC Asia East Continent Online Contest (I) A题(模拟+贪心)
算法·贪心·模拟
小程序设计43 分钟前
【机械设计】磁粉检测机器人的设计与验证
算法·机器人
Navigator_Z1 小时前
LeetCode //C - 1220. Count Vowels Permutation
c语言·算法·leetcode
吃好睡好便好1 小时前
判断函数的使用
学习·算法·matlab·生活·判断函数
2601_956121971 小时前
线性DP(入门)
c++·算法·动态规划
feilieren1 小时前
leetcode - 389. 找不同
算法·leetcode
挽星安1 小时前
2026/8/29
数据结构·算法
positive_zpc1 小时前
进阶数据结构图——最短路径(二)
数据结构·算法·图论·最短路径
positive_zpc2 小时前
进阶数据结构图——最小生成树(一)
数据结构·算法·图论