LeetCode 219:存在重复元素 II——哈希表记录“最近一次出现的位置”

目录

LeetCode 219:存在重复元素 II

一、推荐先做入门的 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 会被覆盖

有些情况下,"覆盖"可能意味着丢失信息。

但在这道题里:

覆盖旧位置恰好就是我们需要做的事情,因为旧位置没有最近一次的位置更有价值。

相关推荐
xiangzhihong81 小时前
创之星花店多端业务闭环拆解
人工智能
奈落242 小时前
AI 编程从助手到 Agent:基于两份资料看哪些环节可以交出去,哪些必须自己攥住
大数据·人工智能
Joker可视化开发平台2 小时前
AI短剧接棒真人剧:开机量跌七成,普通人进场窗口在收窄
大数据·人工智能
澳鹏Appen2 小时前
澳鹏电子书 | 强化学习环境:为AI智能体打造高保真训练场
人工智能
吴佳浩2 小时前
单卡5090跑125B 大模型:Strata 把服务器级 MoE 拉进普通 PC
人工智能
Data-Miner2 小时前
AI做表格软件哪个好?专业评测:五维对比看清差距
人工智能
志尊宝2 小时前
Vue3 零基础每日笔记(073):keep-alive 页面缓存实战——列表页返回不丢状态
vue.js·笔记·缓存·#vue #前端 #前端开发·#javascript·#vue.js
Marst Code2 小时前
上位机开发日记 · 第 2 篇 · 架构先行:六层分层与边界
python
Raspberry_Pi_官方账号3 小时前
观察、理解与响应:Raspberry Pi 5 上的低功耗 CNN、VLM 和 SLM 工作负载
人工智能·神经网络·cnn·树莓派·raspberrypi