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

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

题目链接

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


题目描述

给你一个整数数组 nums 和一个整数 k,判断数组中是否存在两个 不同的索引 i 和 j,满足 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+1 到 i 的元素(共 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 题解和算法干货!

相关推荐
信奥卷王7 小时前
[GESP202609 六级] 分树规划
算法
夏幻灵8 小时前
LeetCode 21:合并两个有序链表——双指针与哑节点详解
算法·leetcode·链表
徐小黑ACG9 小时前
Golang 基础05 结构体struct
开发语言·算法·golang
Navigator_Z10 小时前
LeetCode //C - 1286. Iterator for Combination
c语言·算法·leetcode
北极有牛10 小时前
cpp学习笔记--常量指针
java·开发语言·算法
GPU实战笔记10 小时前
云端 GPU 训练的账户余额提醒:它能说明什么,不能说明什么?
深度学习·算法·成本管理·gpu云计算·云端训练
浮链序10 小时前
怎么证明"你这个模型是偷我的"——把蒸馏变成取证工具
算法·安全·llm
小O的算法实验室10 小时前
IEEE TMC,面向异构物联网的多目标量子行为拓扑优化算法
算法
圣保罗的大教堂11 小时前
leetcode 301. 删除无效的括号 困难
leetcode