LeetCode003 无重复字符的最长子串
1 数组实现
维护一个数组,总是记录无重复子串; 维护一个变量,总是记录当前无重复子串的长度中较长的那个
有重复,就删除当前记录重复子串中导致重复的部分子串0~start。
每次遍历在记录长度时,和当前记录值比较,取较大的那个。
最终记录的是最长子串长度
const lengthOfLongestSubstring = s => {
if (typeof s !== 'string') throw 'TypeError'
const arr = []
return s.split('').reduce((max, cur) => {
const start = arr.indexOf(cur)
if (start !== -1) {
arr.splice(0, start + 1) // 有重复,删除当前记录重复子串中导致重复的部分子串, 如'abca', 则删除第一位'a'
}
arr.push(cur)
return Math.max(max, arr.length)
}, 0)
}
2 滑动窗口
窗口为start, end. 在j处发现有重复值,则将窗口start移动到j+1的位置,毕竟从后一位才开始不重复。
窗口长度(end - start +1) 即为无重复最长子串长度
const lengthOfLongestSubstring = s => {
if (typeof s !== 'string') throw 'TypeError'
let max = 0
for (let start = 0, j = 0; j < s.length; j++) {
const index = s.substring(start, j).indexOf(s[j])
if (index !== -1) {
start = start + index + 1 // substring一直在子串里查找,不会出现重复父串嵌套重复子串的case
}
max = Math.max(max, j - start + 1)
}
return max
}
3 滑动窗口优化
利用map优化indexOf的查找过程,使得时间复杂度变为O(n)
map方式没有截去之前的重复子串,对于相同字符,只会存储较大的那个字符索引
因此在重复父串嵌套重复子串的情况时,要按子串计算, 即设置窗口start的时候要和自身对比取max
const lengthOfLongestSubstring = s => {
if (typeof s !== 'string') throw 'TypeError'
let max = 0
const map = new Map()
for (let start = 0, j = 0; j < s.length; j++) {
const cur = s[j]
if (map.get(cur) !== undefined) {
// +1表示后一位开始才不重复, max表示如果重复父串中包含重复子串,应该按子串计算,如果按父串计算,那么父串会包含重复子串,不符合条件。
// 例如tmmzuxt,父串t~t嵌套了子串m~m, 如果不取max, 那么start最终为1, 显然不对
start = Math.max(map.get(cur) + 1, start)
}
max = Math.max(max, j - start + 1)
map.set(cur, j)
}
return max
}