一、题目描述
给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和 j ,满足:
-
nums[i] == nums[j] -
abs(i - j) <= k
如果存在,返回 true;否则返回 false。
示例 1
输入:nums = [1,2,3,1], k = 3
输出:true
解释:
nums[0] = nums[3] = 1
|0 - 3| = 3 <= k
示例 2
输入:nums = [1,0,1,1], k = 1
输出:true
示例 3
输入:nums = [1,2,3,1,2,3], k = 2
输出:false
二、解题思路
题目的核心是:
找到两个相同元素,并且它们的下标距离不超过
k。
如果直接使用 两层循环:
for i
for j=i+1 ... i+k
时间复杂度为:
O(n*k)
当 n 最大为 10^5 时容易超时。
因此可以使用 哈希表 + 滑动窗口 的思想进行优化。
三、滑动窗口 + 哈希表
核心思想
维护一个 大小最多为 k 的窗口:
-
使用 哈希集合 存储窗口中的元素
-
每次遍历
nums[i]:-
如果当前元素已经存在于集合中 → 说明找到了重复元素
-
否则加入集合
-
-
如果窗口大小超过
k,删除最早进入窗口的元素nums[i-k]
这样集合中始终只保存 最近 k 个元素。
过程示例
nums = [1,2,3,1]
k = 3
遍历过程:
i=0 set={1}
i=1 set={1,2}
i=2 set={1,2,3}
i=3 发现1已经存在 -> 返回true
四、C语言实现(滑动窗口 + 哈希表)
由于 C 语言没有内置 HashSet,我们使用 链式哈希表 来实现。
#include <stdbool.h>
#include <stdlib.h>
#define HASH_SIZE 200003
typedef struct Node {
int val;
struct Node* next;
} Node;
Node* hash[HASH_SIZE];
int hashFunc(int x) {
if (x < 0) x = -x;
return x % HASH_SIZE;
}
bool find(int val) {
int h = hashFunc(val);
Node* cur = hash[h];
while (cur) {
if (cur->val == val)
return true;
cur = cur->next;
}
return false;
}
void insert(int val) {
int h = hashFunc(val);
Node* node = (Node*)malloc(sizeof(Node));
node->val = val;
node->next = hash[h];
hash[h] = node;
}
void removeVal(int val) {
int h = hashFunc(val);
Node* cur = hash[h];
Node* pre = NULL;
while (cur) {
if (cur->val == val) {
if (pre)
pre->next = cur->next;
else
hash[h] = cur->next;
free(cur);
return;
}
pre = cur;
cur = cur->next;
}
}
bool containsNearbyDuplicate(int* nums, int numsSize, int k) {
for (int i = 0; i < HASH_SIZE; i++)
hash[i] = NULL;
for (int i = 0; i < numsSize; i++) {
if (find(nums[i]))
return true;
insert(nums[i]);
if (i >= k)
removeVal(nums[i - k]);
}
return false;
}
五、复杂度分析
时间复杂度
O(n)
每个元素最多进行一次:
-
插入
-
查找
-
删除
空间复杂度
O(k)
哈希表中最多只存储 k 个元素。
六、暴力解法(不推荐)
思路
枚举每个元素,检查后面 k 个位置是否有相同元素。
代码实现
bool containsNearbyDuplicate(int* nums, int numsSize, int k) {
for (int i = 0; i < numsSize; i++) {
for (int j = i + 1; j <= i + k && j < numsSize; j++) {
if (nums[i] == nums[j])
return true;
}
}
return false;
}
复杂度
时间复杂度:O(n*k)
空间复杂度:O(1)
当 n 很大时容易 超时。
七、总结
本题的关键在于理解 滑动窗口思想:
-
只需要维护 最近 k 个元素
-
使用 哈希表快速判断是否重复
核心流程:
遍历数组
如果元素已存在 -> 返回 true
加入哈希表
如果窗口超过 k -> 删除 nums[i-k]
这种 滑动窗口 + 哈希表 的技巧在很多题目中都会出现,例如:
-
LeetCode 217:存在重复元素
-
LeetCode 219:存在重复元素 II
-
LeetCode 220:存在重复元素 III