Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
一、题目描述:
某天我去腾讯面试,他给我出了一道算法题,我当时还没写出来,题目好像是提供一个字符串,需要找到字符串里面无重复的最长子串长度。题目也没啥太多有用的信息,脑瓜子嗡嗡得不知道如何下手。
二、思路分析:
根据题目我们的入参是一个字符串,输出是一个子串的长度,先假设提供的字符串是“abcc”,那么先列举一下所有可能出现的子串:
我们可以想象成3个区间
区间1
ab
abc
abcc
区间2
bc
bcc
区间3
cc
于是就写下的下面的代码:
function lengthOfLongestSubstring (s) {
let len = s.length
if (len <= 1) return len
let maxLen = 1
for (let i = 0; i < len; i++) {
for (let j = i + 1; j < len; j++) {
if (allUnique(s, i, j)) {
maxLen = Math.max(maxLen, j - i + 1)
}
}
}
return maxLen
}
function allUnique(s, start, end) {
let set = new Set()
for (let i = start; i <= end; i++) {
if (set.has(s[i])) {
return false
}
set.add(s[i])
}
return true
}
console.log(todo('abcc'))
暴力解法:遍历数组的所有的区间,然后找到最长没有重复字符的区间,时间复杂度:O(n^3)。
逐个检查所有的子字符串,看他是否还有不重复的字符。生成子字符串可以用两重循环,第一重遍历子串的起点字符,第二重遍历子串的结束字符。对于每个子串再遍历看他是否还有不重复的字符。
由于暴力解法的复杂度比较高,生成子串就需要O(n^2),然后还需要查找子串是否有重复的字符找到最长子串需要O(n),总体需要O(n^3)。
接下来上重点,正确的解法需要用到滑动窗口,先不解释什么是滑动窗口,看完代码再说。
function lengthOfLongestSubstring(s) {
const set = new Set()
const len = s.length
let j = -1
let maxLen = 0
for (let i = 0; i < len; i++) {
if (i != 0) {
set.delete(s[i - 1])
}
while (j + 1 < len && !set.has(s[j + 1])) {
set.add(s[j + 1]);
j++
}
maxLen = Math.max(maxLen, j - i + 1)
}
return maxLen
}
在介绍滑动窗口的框架时候,大家先从字面理解下:
- 滑动: 说明这个窗口是移动的,也就是移动是按照一定方向来的。
- 窗口: 窗口大小并不是固定的,可以不断扩容直到满足一定的条件;
从上的的代码来看,我们从0位置滑动,然后不断扩大窗口直到发现set中重存在重复的字符时停止,此时窗口的大小是3包含字符abc,然后开始滑动到1的位置,注意开始之前先删除a,然后窗口保留bc,从c之后扩大窗口范围。
注意:滑动的位置是一个一个往后移动,但是窗口的大小并不是固定,之前已经计算过的窗口可以留着下次继续使用。
四、总结:
其实这个题目还是挺难的,使用滑动窗口事时间复杂度变成O(N)。如果了解滑动窗口协议也许思路会更清晰。
学过计算机网络的同学,都知道滑动窗口协议(Sliding Window Protocol),该协议是 TCP协议 的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。该协议允许发送方在停止并等待确认前发送多个数据分组。由于发送方不必每发一个分组就停下来等待确认。因此该协议可以加速数据的传输,提高网络吞吐量。