4
先跳到第三题是因为第二题第一眼没读懂

一、题目

无重复字符的最长子串:

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

示例1

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

示例2

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

示例3

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。

请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

二、我的答案

因为这道题之前在学校刷题的时候见过类似的,大概记得解法,就先放自己的思路

v1.0

/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
  let resultLength = 0
  function hasEchoChar(str){
    return new Set(str).size !== str.length ? true : false
  }
  let begin = 0, end = 1;
  while(end <= s.length) {
    if (!hasEchoChar(s.slice(begin, end))) {
      end - begin > resultLength ? resultLength = end - begin : null;
      end++
    } else {
      begin++ 
    }
  }
  return resultLength
};

讲解

  1. 两个指针用来遍历字符串,begin指向当前字符串的头,end指向当前字符串的下一位

    let begin = 0, end = 1
  2. 如果当前字符串不包含重复字符串,则判断是否更新结果(resultLength)且end++,如果包含,begin++

    if (!hasEchoChar(s.slice(begin, end ))) {
      end - begin > resultLength ? resultLength = end - begin : null;
      end++
    } else {
      begin++ 
    }

           上文也说了之前碰到过类似的题,当时理解这部分用了好久,因为常写的循环都是只有一个指针变量的,像for(let i = 0; i < num; i++),如果按照惯用思路肯定是循环套循环遍历所有子串,麻烦的丫批,上面这种写法妙就妙在一次循环中要么更新begin要么更新end,缩小了子串的筛选范围。

           以示例3中的pwwkew为例,遍历的过程如下图
    便利过程

v2.0

写完v1.0代码,开心提交,通过是通过了,但是
v2.0产生的原因
       我:???怎么肥四,一顿乱吹然后战胜8%,神仙打架?仔细分析了一下,发现是判断是否包含重复字符的函数hasEchoChar的问题,在v1.0代码中我把子串转化成set结构然后对比前后length是否相等,因为set自动去重的特性,如果相等则没有重复字符。以此来实现判断是否包含重复字符。(这么写的原因是上一题做完后去看了es6中Set和Map一章,上一题分析连接:我是链接)

function hasEchoChar(str){
    return new Set(str).size !== str.length ? true : false
}

       估计耗时就是耗在了转化成set结构了,这样写本身并没错,但是他的作用是判断一个任意字符串是否含有重复字符,我们每次判断的是任意字符串吗?并不是,那就不应该继续采用这种方法。
       聚光灯打到上文中展示遍历过程那张图,有重复字符的是pw->pww和wke->wkew,也就是说,只有每次end++时才有可能产生重复字符,也即是说我们只需要判断上次遍历的字符串是否包含这次新添加的字符即可,思路理清,代码如下

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function (s) {
  let resultLength = 0
  let begin = 0, end = 1
  while (end <= s.length) {
    let index = s.slice(begin, end - 1).indexOf(s[end - 1])
    if (index !== -1) {
      begin += (index + 1)
      end++
    } else {
      end - begin > resultLength ? resultLength = end - begin : null;
      end++
    }
  }
  return resultLength
};

       同时做出优化的部分还有找到相同字串的情况

if (index !== -1) {
  begin += (index + 1)
  end++
}

       在v1.0的代码中,每次找到相同字符就将begin指到下一个位置,如果还有呢,就再指向下一个位置,很明显begin可以一次指到位,即指到与当前子串末位相同字符位置的下一位,同时end++,开始下一轮的崭新循环。

       至此这道题我已经竭尽所能,执行用时从v1.0的956ms降低到v2.0的132ms,击败提交也从8.77%涨到71.88%。但是贪婪的我并不能满足于71.88%,于是去复制了一份耗时最低的答案自己提交,发现耗时140ms甚至比我的耗时还要多?去查了一下(查询结果),可能原因为:1、服务器负载;2、测试用例增加。
       奥~~这样啊
       那对不起,我的就是最佳答案
—————2021/3/3更新—————————————————
我以前是真的菜啊。。。第一题真是白做了,每次调用 indexOf 还有个 O(n) 复杂度呢,还搁这儿最佳答案,给自己看笑了

var lengthOfLongestSubstring = function(s) {
  var map = {}
  var max = 0
  for(let begin = 0, end = 0; end < s.length; end++) {
    if(map[s[end]] === undefined) {
      map[s[end]] = end
    } else {
      begin = Math.max(map[s[end]] + 1, begin)
      map[s[end]] = end
    }
    max = Math.max(max, end - begin + 1)
  }
  return max
};

三、优秀答案

/**
 *. @param {string} s
 *. @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    let substr = '', maxLength = 0;
    for (var i = 0; i < s.length; i++) {
        let findIndex = substr.indexOf(s[i]);
        if (~findIndex) {
            substr = substr.substring(findIndex + 1);
        }
        substr += s[i];
        if (substr.length > maxLength) {
            maxLength = substr.length;
        }
    }
    return maxLength;
};
  1. 按位取反~
    之前从没见过~符号,搜了一下,是什么补码之类的,应该是大学计算机原理讲的东西(流下了悔恨的泪水),感兴趣的小伙伴可以看一下这个js中怎么理解按位取反?
  2. substring
    功能和slice相同类似,同样,感兴趣的小伙伴可以看一下MDN中substring的定义

四、路漫漫其修远兮


cvSoldier
119 声望13 粉丝

原地tp