1. 题目
题目描述
给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个不同的下标 i 和 j,满足:
-
nums[i] == nums[j] -
(abs(i-j) <= k)
示例
输入:nums = 1,2,3,1, k = 3
输出:true
输入:nums = 1,0,1,1, k = 1
输出:true
输入:nums = 1,2,3,1,2,3, k = 2
输出:false
约束
-
(1 <= nums.length <= 10^5)
-
(-10^9 <= numsi <= 10^9)
-
(0 <= k <= 10^5)
2. 最佳解题思路描述(哈希表存储上一次出现下标,一次遍历)
-
哈希表
unordered_map<int, int>:key = 数组数值,value = 该数值上一次出现的下标; -
逐个遍历数组,当前下标i、数值x=numsi:
-
如果哈希表存在
x:计算当前下标与上次下标差值,若 ≤k 直接返回 true; -
无论是否匹配成功,都更新哈希表中
x对应的下标为当前i(只保留最近一次下标,后续更易满足间距条件); -
不存在则插入当前数值与下标;
-
-
遍历结束无满足条件则返回 false。
优势
时间 (O(n)),仅一次遍历;空间 (O(n)) 最坏;逻辑直观,适合大数据量。
3. 可优化代码(逻辑正确,分支冗余,结构繁琐)
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
unordered_map<int,int> hash;
for(int i = 0;i<nums.size();i++){
auto it = hash.find(nums[i]);
if(it != hash.end()){
if(abs(i-it->second)<=k){
return true;
}
it->second = i;
}
if(it == hash.end()){
hash.insert({nums[i],i});
}
}
return false;
}
};
代码说明
-
正确性:无逻辑 bug,全部用例可通过;
-
冗余 / 可优化点:
-
两次
hash.find查找,重复查询哈希,浪费性能; -
if(it != hash.end())与if(it == hash.end())互斥,可用 else 简化; -
命名
hash改为lastIdx语义更清晰; -
插入更新可统一简写
hash[nums[i]] = i,无需区分 insert; -
提前缓存
nums[i]减少重复取值。
-
4. 规范简化版代码(哈希表最优写法)
#include <unordered_map>
#include <vector>
#include <cmath>
using namespace std;
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
unordered_map<int, int> lastIdx;
int n = nums.size();
for (int i = 0; i < n; ++i) {
int x = nums[i];
auto it = lastIdx.find(x);
if (it != lastIdx.end()) {
if (abs(i - it->second) <= k) {
return true;
}
}
// 无论存在与否,都更新为当前下标
lastIdx[x] = i;
}
return false;
}
};
5. 总结
-
核心规则:相同数字只保留最近一次下标,差值最小,更容易满足间距≤k;
-
关键优化:不用分开 insert / 更新,直接赋值
map[key]=val自动覆盖旧值; -
代码分支判断重复查询哈希,可读性与效率较差,用 else 合并分支更简洁;
-
找到满足条件的一对下标可直接 return,无需遍历完整数组。
6. 相关知识拓展
拓展 1:滑动窗口集合解法(空间更省,窗口大小 k)
维护窗口内最多 k 个元素,集合只保存滑动窗口内数字,空间 (O(k)):
#include <unordered_set>
bool containsNearbyDuplicate(vector<int>& nums, int k) {
unordered_set<int> win;
int n = nums.size();
for (int i = 0; i < n; ++i) {
if (win.count(nums[i])) return true;
win.insert(nums[i]);
// 窗口超过k,移除窗口最左侧元素
if (win.size() > k) {
win.erase(nums[i - k]);
}
}
return false;
}
拓展 2:复杂度对比
-
哈希存储上一次下标(优化版):(O(n)) 时间,(O(n)) 空间;
-
滑动窗口集合:(O(n)) 时间,(O(k)) 空间,k 较小时更省内存。
拓展 3:易错点复盘
-
匹配成功后忘记更新当前下标,后续判断会和旧下标对比,出现错误;
-
重复执行
find多次查询哈希,性能冗余; -
下标差值忘记取绝对值(i 一定大于上次下标,本题可省略 abs,但规范建议保留)。