题目描述: 给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引
i 和 j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false 。
示例 1:
输入: nums = [1,2,3,1], k = 3
输出: true
示例 2:
输入: nums = [1,0,1,1], k = 1
输出: true
示例 3:
输入: nums = [1,2,3,1,2,3], k = 2
输出: false
思路 1—— 暴力法(入门理解)
核心逻辑:遍历每个元素,检查它后面最多 k 个元素是否和它重复。
代码示例:
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
int n = nums.size();
// 边界:数组长度<2 或 k=0(i≠j,索引差不可能≤0),直接返回false
if (n < 2 || k == 0) return false;
for (int i = 0; i < n; ++i) {
// j最多到i+k,且不超过数组末尾
for (int j = i + 1; j <= min(i + k, n - 1); ++j) {
if (nums[i] == nums[j]) {
return true;
}
}
}
return false;
}
};
缺点:
时间复杂度 O (nk),如果 k 接近数组长度 n(比如 n=10^5,k=10^5),会超时,实际面试 / 刷题中不推荐。
思路 2—— 滑动窗口 + 哈希表(最优解)
核心逻辑:
-
维护一个「大小不超过 k」的滑动窗口(窗口内元素的索引差都≤k);
-
用
unordered_set存储窗口内的元素(快速判断当前元素是否在窗口中); -
遍历数组时:
- 若当前元素在 set 中 → 说明存在重复且索引差≤k,返回 true;
- 若不在,将当前元素加入 set;
- 若 set 大小超过 k → 移除窗口最左侧的元素(保证窗口大小≤k)。
代码实现(高效且简洁):
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
int n = nums.size();
if (n < 2 || k == 0) return false;
unordered_set<int> window; // 存储窗口内的元素
for (int i = 0; i < n; ++i) {
// 1. 检查当前元素是否在窗口中(满足条件直接返回)
if (window.count(nums[i])) {
return true;
}
// 2. 加入当前元素到窗口
window.insert(nums[i]);
// 3. 窗口大小超过k,移除最左侧的元素(i-k位置的元素)
if (window.size() > k) {
window.erase(nums[i - k]);
}
}
// 遍历完无满足条件的元素
return false;
}
};
关键解释:
- 窗口的「左边界」是
i - k,右边界是i,保证窗口内所有元素的索引差≤k; unordered_set的count/insert/erase操作平均时间复杂度都是 O (1),整体时间复杂度 O (n),空间复杂度 O (k)(最多存储 k 个元素);- 边界处理:提前判断
n<2(无两个元素)、k=0(索引差必须 > 0),避免无效遍历。
对比两种解法:
| 解法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 暴力法 | O(nk) | O(1) | k 很小(比如 k≤100) |
| 滑动窗口 + 哈希 | O(n) | O(k) | 任意 k(推荐) |