题目
3. 无重复字符的最长子串 - 力扣(LeetCode)
解题思路
思路
方法: 滑动窗口
[!简单思路]
[^1]以示例一中的字符串 abcabcbb 为例,找出==从每一个字符开始的,不包含重复字符的最长子串==,其中最长的那个字符串即为答案。
对于示例一中的字符串,我们列举出这些结果,其中括号中表示选中的字符以及最长的字符串:
以 (a)bcabcbb 开始的最长字符串为 (abc)abcbb;
以 a(b)cabcbb 开始的最长字符串为 a(bca)bcbb;
…
[!为什么可以使用滑动窗口]
依次递增地枚举子串的起始位置,那么子串的结束位置也是递增的!这里的原因在于,假设我们选择字符串中的第 k 个字符作为起始位置,并且得到了不包含重复字符的最长子串的结束位置为 r
k。那么当我们选择第 k+1 个字符作为起始位置时,首先从 k+1 到 r
k的字符显然是不重复的,并且由于少了原本的第 k 个字符,我们可以尝试继续增大 r
k,直到右侧出现了重复字符为止。
这样一来,我们就可以使用「滑动窗口」来解决这个问题了:
具体实现
分析Set方法在最长无重复子串问题中的应用
在本题中,使用了哈希集合的
has(value)
:
- 用于检查集合中是否存在某个字符。在代码中,`occ.has(s.charAt(rk + 1))`用于判断当前字符是否已经存在于集合中。
-
add(value)
:- 用于将一个字符添加到集合中。在代码中,
occ.add(s.charAt(rk + 1))
用于将新遇到的字符添加到集合中。
- 用于将一个字符添加到集合中。在代码中,
-
delete(value)
:- 用于从集合中删除某个字符。在代码中,
occ.delete(s.charAt(i - 1))
用于在窗口左指针向右移动时,删除窗口左边界的字符。
- 用于从集合中删除某个字符。在代码中,
-
滑动窗口法:
-
使用双指针(
i
和rk
)来维护一个滑动窗口,确保窗口内的字符都是不重复的。 -
i
表示窗口的左边界,rk
表示窗口的右边界。
-
-
Set
的使用:-
occ
是一个Set
,用于存储当前窗口中的字符。 -
在外层循环中,每次左指针
i
移动时,会删除前一个左边界字符。 -
内层循环中,右指针
rk
不断向右移动,直到遇到重复字符为止。
-
AC代码
var lengthOfLongestSubstring = function(s) {
const occ = new Set(); // 创建哈希集合, 用户判断字符串是否重复
const n = s.length; // 记录下s字符串的长度
// 初始化右指针为 rk = -1, 表示右指针初始状态在字符串左边缘的左侧, 这样比较合理
let rk = -1, ans = 0; // ans用来记录最终最大字符串的长度
for(let i = 0; i < s.length; i++) {
// 左指针向右移动一格, 移除一个字符
if(i != 0) {
occ.delete(s.charAt(i - 1));
}
// 右指针不越界并且右指针不是重复字符时
while (rk + 1 < n && !occ.has(s.charAt(rk + 1))) {
occ.add(s.charAt(rk + 1));
++rk;
}
ans = Math.max(ans, rk - i + 1)
}
return ans;
}
rk - i + 1
:
- 当前窗口的长度是右指针
rk
减去左指针i
加 1。这是因为索引是从 0 开始的,比如从索引i
到rk
的字符总共有rk - i + 1
个。
Math.max(ans, rk - i + 1)
:
ans
保存的是当前找到的最长无重复子串的长度。
Math.max
会比较ans
和当前窗口的长度,取较大的值作为新的ans
,确保ans
始终记录最长的窗口长度。
判断重复字符
使用哪种数据结构来判断 是否有重复的字符?
——JavaScript
中的Set
在 JavaScript 中,要判断字符串中是否包含重复字符,可以使用Set
数据结构。Set
是一种特殊的数据结构,它存入的值都是唯一的,不会有重复。
[!Set 判断字符串中是否有重复字符的步骤]
步骤:
1 将字符串转为字符数组
(将字符串拆分为单个字符组成的数组,以便逐个检查字符)
2 使用 Set 存储字符:
将字符数组中的每个字符存入 Set 中
3 比较 Set 的大小和原字符串的长度
如果 Set 的大小和原字符串的长度不同,说明存在重复字符,因为 Set 会自动去重.