[JavaScript / leetcode] 3. 无重复字符的最长子串

56 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第25天,点击查看活动详情

每日刷题 2022.10.26

题目

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

示例

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

提示

  • 0 <= s.length <= 5 * 104
  • s 由英文字母、数字、符号和空格组成

解题思路

  • 根据题目的含义:需要找到一个最长的且不重复的连续子串。
  • 此时想到:既然是需要找到一个连续的子串,那么就可以将其当作一个区间来看,那么这个符合要求的区间长度中取最大值就可以了。
  • 那么如何找到这样的区间呢?其实直接双层for循环就可以实现,但是这样会浪费掉很多已经可以利用起来的子区间,例如:对于字符串acd而言:ac是满足要求的区间,加上d其也是满足要求的区间,那么第一次遍历到满足的区间ac后,就只需要再后面再添加上d就可以了,无需从头再次遍历a然后ac,最后acd
  • 故:就想到了使用滑动窗口(双指针的解法),配合使用set集合。使用set集合记录区间内出现过的字母,然后使用滑动窗口根据set中统计的数据来进行滑动。
  • 具体的:当右指针r指向的当前值cur是已经出现在set集合中,那么就移动左指针l(每移动一次l指针,就表示从区间内删除一个值,相应的也需要将其从set集合中删除),当移动左指针lset集合中不包含当前的字符为止(!此时的lr之间包含的区间一定是满足的!!)。此时再次移动r,循环上述步骤。

注意事项

  • 因为题目中要求找到最长的,因此我们通常会想到:当第一次找到不满足的时候,再统计区间的长度,那一定是最大的,但是这样就会漏掉一种情况:如果到结尾(数组的末尾),都是符合要求的(即:没有碰到不符合要求的),这样的话不就漏掉了。
  • 解决方法:不管是不是最长的,只要满足就要取max,这样就可以将每一种符合要求的区间长度都记录下来。

拓展

  • 滑动窗口:若当前区间内满足要求,则将右指针r一直往后移,当遇到第一个不满足的时候,停止移动r, 接着移动左指针l,直到区间内再次满足要求。
  • 那么请问:当数组中存在负值的时候,还能使用滑动窗口来处理区间和的大小的问题吗?答案:不能。因为存在负值的情况下,就不能满足右指针r向后移动,一定是大于等于某个值的,同理也不能保证左指针l向前移动,一定是小于等于某个值的。
  • 那么不能使用滑动窗口如何解决这样的问题呢?就可以考虑下前缀和是否适用。

AC代码

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
  // 使用set集合+双指针解决
  let set = new Set(), l = 0, r = 1, n = s.length, maxx = 1;
  if(n === 0) return 0;
  set.add(s[l]);
  while(r < n) {
    let cur = s[r];
    while(set.has(cur) && r < n) {
      let t = s[l];
      set.delete(t);
      l++;
    }
    r++;
    set.add(cur);
    maxx = Math.max(maxx, r - l);
  }
  return maxx;
};