3. 无重复字符的最长子串
原文题目
给定一个字符串 s 请你找出其中不含有重复字符的最长子串的长度。
https://leetcode.cn/problems/longest-substring-without-repeating-characters/
题目分析
博主个人觉得这道题相对前面两道题会难很多。
题目中最难理解的部分应该就是,“不含有重复字符的最长子串的长度” 这句话了。下面将逐步拆解分析。
子串
假设有一个字符串 pwwkew 它的子串如下:
pww+kew=pwwkewpw+wk+ew=pwwkewp+wwkew=pwwkew
有太多种组合了,这边就不一一列举了。其实想说的就是,像 pww kew 这种就是子串了。
不含有重复字符
例如子串 pww 就不符合啦,题目要求子串里面的字母不能重复。
最长子串的长度
其实可以从肉眼上看出来,pwwkew 满足题目要求的最长的子串应该是 wke kew,只要返回他们其中一个的长度即可,长度是 3
题解思路
方式一:滑动窗口算法
可以利用滑动窗口,双指针的算法来实现,只要算出最佳的窗口位置,用 p2 - p1 就能求出窗口长度,即最长子串的长度。
js
const lengthOfLongestSubstring = function (s) {
const map = new Map()
let p1 = -1
let length = 0
for (let p2 = 0; p2 < s.length; p2++) {
let val = s[p2]
if (map.has(val)) {
p1 = Math.max(map.get(val), p1)
}
length = Math.max(p2 - p1, length)
map.set(val, p2)
}
return length
}
lengthOfLongestSubstring('pwwkew')const lengthOfLongestSubstring = function (s) {
const map = new Map()
let p1 = -1
let length = 0
for (let p2 = 0; p2 < s.length; p2++) {
let val = s[p2]
if (map.has(val)) {
p1 = Math.max(map.get(val), p1)
}
length = Math.max(p2 - p1, length)
map.set(val, p2)
}
return length
}
lengthOfLongestSubstring('pwwkew')时间复杂度:O(n)
空间复杂度:O(n)
思路分析
- 首先定义两个指针
p1 = -1p2 = 0此时的滑动窗口范围就是-1 ~ 0范围结果就是p,开始遍历字符串pwwkew,将p2指针逐步+1
- 第一次循环
p2 = 1,滑动窗口范围就是-1 ~ 1范围结果是pw
- 第二次循环
p2 = 2,滑动窗口范围就是-1 ~ 2范围结果是pww
但是滑动窗口中已经存在
w,所以p1的位置就要调整为已存在元素的下标+1。滑动窗口中之前的w下标为1,所以p1 = 1 + 1第三次循环
p1 = 2p3 = 3,滑动窗口范围就是2 ~ 3范围结果是wk
以此类推,直到
for循环执行完毕,每次for循环的过程中都需要更新滑动窗口的长度,取最大值。以上为大致的思路,具体效果因代码实现而微调。
代码分析
- 首先创建双指针
p1p2用于存储滑动窗口下标。以及map数据结构用于存储字符串每个字符的下标。
js
const map = new Map()
let p1 = -1
let length = 0const map = new Map()
let p1 = -1
let length = 0- 然后遍历字符串
s,其中p2为滑动窗口的末尾下标,p1则是起始下标。遍历的过程中将当前字符和下标存储到数据结构map中。
js
for (let p2 = 0; p2 < s.length; p2++) {
let val = s[p2]
map.set(val, p2)
}for (let p2 = 0; p2 < s.length; p2++) {
let val = s[p2]
map.set(val, p2)
}- 每一次遍历中,用
p2-p1计算窗口的长度。例如有一个矩形,你想计算它的长度,是不是可以通过右侧的x坐标,减去左侧的x坐标即可,滑动窗口也是一样的方式。题目中要求求出最大的长度,所以需要配合Math.max来实现。
js
length = Math.max(p2 - p1, length)length = Math.max(p2 - p1, length)p2是窗口的末尾下标,而且是通过for循环进行+1,没有其他方式改变它,基本上就是固定的输出 0 ~ 字符串的长度。但是p1不同,需要根据条件动态调整位置。当遍历的过程中,遇到重复的字符,则将p1的下标调整为数据结构map中已存在元素的下标。但是如果p1的下标本身就大于map中已存在元素的下标,则不需要调整位置。
js
if (map.has(val)) {
p1 = Math.max(map.get(val), p1)
}if (map.has(val)) {
p1 = Math.max(map.get(val), p1)
}