目录
- [一、推荐先做入门的 217](#一、推荐先做入门的 217)
- 二、哈希表里到底应该保存什么?
- [三、同一个数字出现很多次,key 怎么办?](#三、同一个数字出现很多次,key 怎么办?)
- 四、为什么要保存"最近一次出现的位置"?
- 五、为什么只保存"最近一次"就够了?
- 六、按照这个思路,我一开始写出的代码
- [七、这里其实不需要 abs()](#七、这里其实不需要 abs())
- 八、代码还能继续简化
- 九、最后代码
- 十、这道题和前面的哈希表题有什么关系?
一、推荐先做入门的 217
我之前做过 217「存在重复元素」。
这道题算是入门的,用于理解怎么使用哈希表中的 set。链接是这个
当时 217 只需要判断:
当前数字以前有没有出现过?
所以一个 set 就够了。
例如:
python
seen = set()
每次扫描当前数字时:
text
以前见过 → True
以前没见过 → 加入 seen
但 219 又多了一个条件:
两个相同元素的下标之差必须小于等于 k。
也就是说,这道题不只是问:
text
这个数字以前有没有出现过?
还要继续问:
text
它上一次出现在哪里?
所以只用 set 已经不够了。
因为 set 只能告诉我 有没有,却不能告诉我 在哪里。
这时候就自然而然地想到了哈希表中的另外一种形式:
python
dict
二、哈希表里到底应该保存什么?
既然题目最后要比较:
text
两个相同数字之间的下标距离
那么字典最自然可以保存:
text
数字 → 下标
例如:
text
1 → 3
2 → 5
4 → 7
表示:
text
数字 1 出现在下标 3
数字 2 出现在下标 5
数字 4 出现在下标 7
于是当我扫描到当前:
python
nums[i]
时,如果这个数字以前出现过,就可以直接拿出之前保存的下标,计算:
python
i - groups[num]
然后判断:
text
<= k
就可以了。
不过这里很容易出现一个新的问题:
如果同一个数字出现很多次,那
dict里的 key 不就重复了吗?
三、同一个数字出现很多次,key 怎么办?
比如:
text
nums = [1, ..., 1, ..., 1]
数字 1 可能出现在:
text
下标 0
下标 5
下标 7
那字典是不是要变成:
text
1 → 0
1 → 5
1 → 7
?其实不行。
因为字典里的:
text
key
是不能重复的。
如果已经有:
python
groups[1] = 0
后面又写:
python
groups[1] = 5
并不会出现两个:
text
1
而是会直接把原来的 value 覆盖掉:
text
原来:1 → 0
重新赋值以后:1 → 5
也就是说:
相同 key 再次赋值时,会更新这个 key 对应的 value。
那这里新的问题就来了:
我们到底应不应该覆盖旧下标?
答案是:应该。
而且这正好是这道题真正关键的地方。
四、为什么要保存"最近一次出现的位置"?
还是看刚才这个例子。
数字 1 分别出现在:
text
下标 0
下标 5
下标 7
假设:
text
k = 3
第一次扫描到下标 0:
text
1 → 0
先记录下来。
之后扫描到下标 5。
因为 1 已经在字典里面,所以计算:
text
5 - 0 = 5
大于:
text
3
所以现在还不能返回 True。
那这时候要不要继续保留:
text
1 → 0
?
如果一直保留下标 0,等之后扫描到下标 7 时:
text
7 - 0 = 7
还是不满足。
但实际上:
text
7 - 5 = 2
已经满足:
text
<= 3
所以在扫描到下标 5 时,虽然这一次还不能返回 True,但应该把:
text
1 → 0
更新成:
text
1 → 5
这样后面来到下标 7 时,比较的就是:
text
7 - 5 = 2
于是就能正确找到答案。
所以这里字典真正应该保存的并不是:
text
数字 → 第一次出现的位置
而是:
text
数字 → 最近一次出现的位置
五、为什么只保存"最近一次"就够了?
这里还可以继续想一步。
假设当前下标是:
text
i
同一个数字以前出现过很多次:
text
p1 < p2 < p3 < ... < i
那么离当前 i 最近的,一定是:
text
最后一次出现的位置
也就是最大的那个下标。
比如:
text
以前出现的位置:
0
5
8
现在的位置:10
那么距离分别是:
text
10 - 0 = 10
10 - 5 = 5
10 - 8 = 2
最近的一定是:
text
8
所以如果连:
text
i - 最近一次出现的位置
都已经大于 k,那么更早的位置只会离得更远,更不可能满足要求。
也就是说:
text
最近一次都不行
↓
更早的更不可能行
因此:
对于每个数字,只保留最近一次出现的位置就够了。
而 dict 相同 key 重新赋值时会覆盖旧 value 的特性,在这里反而正好符合我们的需求。
每次都可以:
python
groups[num] = i
让它始终保存:
text
num → 最新下标
六、按照这个思路,我一开始写出的代码
想清楚这一点以后,我一开始写的是:
python
class Solution:
def containsNearbyDuplicate(self, nums: list[int], k: int) -> bool:
groups = {}
for i, num in enumerate(nums):
if num not in groups:
groups[num] = i
else:
if abs(groups[num]-i) <= k:
return True
else:
groups[num] = i
return False
整个过程就是:
text
扫描当前数字
↓
以前没出现过 → 记录当前下标
以前出现过
↓
拿出之前保存的下标
↓
计算两个位置之间的距离
↓
距离 <= k → True
距离 > k
↓
旧位置已经没有继续保留的必要
↓
更新成当前位置
这套逻辑本身是可以的。
七、这里其实不需要 abs()
我一开始写的是:
python
abs(groups[num] - i)
但后来发现这里其实不需要 abs()。
因为我们一直按照:
text
从左到右
扫描数组。
以前保存的位置一定是在当前 i 的左边。
也就是说:
text
groups[num] <= i
所以:
python
i - groups[num]
一定不会是负数。
因此可以直接写:
python
i - groups[num]
而不需要:
python
abs(groups[num] - i)
八、代码还能继续简化
再看一遍我原来的写法:
python
if num not in groups:
groups[num] = i
else:
if groups[num] 和 i 的距离 <= k:
return True
else:
groups[num] = i
这里其实有重复。
因为:
第一次出现时
要做:
python
groups[num] = i
以前出现过,但是距离太远时
最后还是要做:
python
groups[num] = i
也就是说,只要没有:
python
return True
当前下标最终都应该成为:
text
这个数字最近一次出现的位置
所以:
python
groups[num] = i
完全可以统一放到最后。
于是原来的:
text
没出现过
→ 保存
出现过但距离太远
→ 更新
可以合并成:
text
只要这一次没有找到答案
→ 一律把当前位置保存成最新位置
这样代码就会简单很多。
九、最后代码
python
class Solution:
def containsNearbyDuplicate(self, nums: list[int], k: int) -> bool:
groups = {}
for i, num in enumerate(nums):
if num in groups and i - groups[num] <= k:
return True
groups[num] = i
return False
整个过程现在可以压缩成:
text
扫描当前数字
↓
这个数字以前出现过?
↓
有
→ 看当前下标和最近一次出现位置的距离
↓
距离 <= k
→ True
否则
↓
groups[num] = i
↓
把当前位置更新成这个数字最新的位置
这里:
python
groups[num] = i
虽然只有一行,但其实同时处理了两种情况:
text
第一次出现 → 新增一个 key-value
已经出现过 → 用当前下标覆盖旧 value
最终 groups 始终保持:
text
数字 → 最近一次出现的下标
十、这道题和前面的哈希表题有什么关系?
这道题让我进一步理解:
dict 的 value 不只是"随便保存一个下标"。
具体保存什么,要看题目真正需要什么信息。
比如:
text
LC 242 有效的字母异位词
字符 → 出现次数
text
LC 1 两数之和
数字 → 下标
而这道 219 是:
text
数字 → 最近一次出现的下标
这里"最近一次"非常重要。
因为题目关心的是:
text
相同数字之间的最近距离
而且这道题也让我更清楚 dict 中重复 key 的处理方式:
text
key 不会重复保存
↓
再次给同一个 key 赋值
↓
原来的 value 会被覆盖
有些情况下,"覆盖"可能意味着丢失信息。
但在这道题里:
覆盖旧位置恰好就是我们需要做的事情,因为旧位置没有最近一次的位置更有价值。