无重复字符的最长子串

694 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

一、题目描述:

某天我去腾讯面试,他给我出了一道算法题,我当时还没写出来,题目好像是提供一个字符串,需要找到字符串里面无重复的最长子串长度。题目也没啥太多有用的信息,脑瓜子嗡嗡得不知道如何下手。

二、思路分析:

根据题目我们的入参是一个字符串,输出是一个子串的长度,先假设提供的字符串是“abcc”,那么先列举一下所有可能出现的子串:

我们可以想象成3个区间

截屏2022-03-05 下午10.34.59.png

区间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之后扩大窗口范围。

截屏2022-03-06 下午10.16.27.png

注意:滑动的位置是一个一个往后移动,但是窗口的大小并不是固定,之前已经计算过的窗口可以留着下次继续使用。

四、总结:

其实这个题目还是挺难的,使用滑动窗口事时间复杂度变成O(N)。如果了解滑动窗口协议也许思路会更清晰。

学过计算机网络的同学,都知道滑动窗口协议(Sliding Window Protocol),该协议是 TCP协议 的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。该协议允许发送方在停止并等待确认前发送多个数据分组。由于发送方不必每发一个分组就停下来等待确认。因此该协议可以加速数据的传输,提高网络吞吐量。