LeetCode 219. 存在重复元素 II(滑动窗口 + 哈希表详解)
题目链接
题目描述
给你一个整数数组 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^90 <= k <= 10^5
解题思路
这道题是 LeetCode 217 的进阶版本,增加了 索引距离限制 。我们需要判断是否存在两个相同元素,且它们的索引差不超过 k。
核心思想:滑动窗口 + 哈希集合
维护一个大小为 k 的滑动窗口,窗口内存储最近 k 个元素:
- 遍历数组,对于当前元素
nums[i]:- 检查它是否已经在窗口(哈希集合)中。
- 如果在窗口中 ,说明找到了两个相同元素,且索引差不超过
k,返回true。 - 如果不在窗口中,将其添加到窗口中。
- 维护窗口大小:当窗口大小超过
k时,移除最旧的元素(nums[i - k])。 - 遍历结束后,返回
false。
为什么使用滑动窗口?
因为题目要求 abs(i - j) <= k,这意味着两个相同元素的索引差不能超过 k。滑动窗口正好可以维护最近 k 个元素:
- 窗口大小始终不超过
k。 - 窗口内的元素与当前元素的索引差都不超过
k。 - 如果当前元素在窗口内,说明找到了满足条件的重复元素。
算法步骤
- 创建一个空的哈希集合
window。 - 遍历数组,对于索引
i:- 如果
nums[i]在window中,返回true。 - 将
nums[i]添加到window中。 - 如果
window.size() > k,移除nums[i - k]。
- 如果
- 遍历结束后,返回
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 题。
总结
这道题是滑动窗口 + 哈希表的经典应用,核心要点:
- 滑动窗口 :维护最近
k个元素的集合。 - 哈希集合:快速判断元素是否在窗口中。
- 窗口维护 :当窗口大小超过
k时,移除最旧的元素。
关键点回顾
- 使用哈希集合作为滑动窗口
- 检查当前元素是否在窗口中
- 维护窗口大小不超过 k
- 时间复杂度 O(n),空间复杂度 O(min(n,k))
滑动窗口是解决数组和字符串问题的常用技巧,掌握后可以解决很多相关问题。
如果觉得本文对你有帮助,欢迎点赞、收藏、关注,我会持续更新更多 LeetCode 题解和算法干货!