LeetCode 3 无重复字符的最长子串 (Tag:Hash Table Difficulty:Medium)

194 阅读2分钟

这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

前言

关于 LeetCode 数组类型题目的相关解法,可见LeetCode 数组类型题目做前必看,分类别解法总结了题目,可以用来单项提高。觉得有帮助的话,记得多多点赞关注哦,感谢!

题目描述

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1: 输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

示例 4:
输入: s = ""
输出: 0

链接:leetcode-cn.com/problems/lo…

题解

  1. 暴力求解.暴力求解的方法很好理解, 就是每次从当前元素向后遍历, 用一个 map 来保存每个出现的字符, 直到遍历到 map 数组已经保存了的字符, 说明找到了以当前元素为起点的子串. 我们找出每个元素的最长子串, 得到最后的值.

    具体代码如下, 时间复杂度 O(n^2)

/**
 * @param {string} s
 * @return {number}
 */
 */
var lengthOfLongestSubstring = function(s) {
  const n = s.length
  let ans = 0
  for (let i = 0; i < n; i++) {
      let map = new Map()
      let j = i
      while (j < n && !map.has(s[j]))  {
          map.set(s[j], 1)
          j++
      }
      ans = Math.max(ans, j - i)
  } 
  return ans
};
  1. 滑动窗口. 在暴力方法中, 我们对于每个元素, 都从头开始寻找最长的子串, 其实, 这中间有重复比较, 因为会重复比较已经比较过的部分, 有点类似于 KMP 算法的意思. 我们可以维护一个窗口, 窗口里的元素一定是不重复. 其实只需要我们在 map 中保存每个字符出现的下标, 当再次出现这个字符时, 只需要让窗口的左边移动到保存好的下标的下一位, 那么就可以保证窗口还是正确的. 当然, 移动之前, 需要将这个字符下标之前的字符 map 标记清除, 保证 map 和窗口里的字符是对应的.

    具体代码如下, 时间复杂度 O(n)

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
  const n = s.length
  let map = new Map()
  let ans = 0
  let l = r = 0
  while (r < n) {
      // 扩大窗口
      while (r < n && !map.has(s[r])) {
          map.set(s[r], r)
          r++
      }
      ans = Math.max(ans, r - l)
      if (map.has(s[r])) {
          now = map.get(s[r]) + 1
          // 删除非窗口字符的 map 标记
          for (let i = l; i < now; i++) {
              map.delete(s[i])
          }
          // 更新左窗口
          l = now
      }
  }
  
  return ans
};