LeetCode刷题挑战第三题-无重复字符的最长子串(JS)

353 阅读4分钟

题目

无重复字符的最长子串(难度-中等)>>

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

示例

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

提示:

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

错误解法

一上来没看懂题意,闹了乌龙,以为是区不重复字符的个数💣💣💣

//错误解法!!!不含有重复字符的最长子串的长度,而不是不重复的字符串
var lengthOfLongestSubstring = function(s) {
    s = s.split('');
    let newS = [];
   for(let i=0;i<s.length;i++){
       if(!newS.includes(s[i])){
           newS.push(s[i])
       }
   }
   return newS.length;
};

解答

题解1: 暴力求解

思路

很容易想到,我们可以将所有字串都列举出来,然后获得最长字串的长度,就是我们的答案。

  1. 将字符串s转为数组(arr),声明len为数组长度。
  2. 如果s长度为0或1,直接返回len。
  3. 外层循环定义str存放子串内容,默认存放了当前元素。内部循环判断str是否存在下一个元素。如果存在,更新最大长度值,跳出本次循环。如果不存在放入str中,然后更新最大长度。
  4. 返回最大子串长度。
/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    const arr = Array.from(s);
    const len = arr.length;
    if(len <2) return len;
    let maxLen = 0;
    for(let i=0;i<=len;i++){//这里执行了n次
        let str = [arr[i]];
        for(let j=i+1;j<len;j++){//这里执行了n*n次
            if(str.includes(arr[j])){
                maxLen = Math.max(str.length,maxLen)
                break
            }
            str.push(arr[j]);
            maxLen = Math.max(str.length,maxLen)
        }
    }
    return maxLen;
};

image.png 贴一下运行打印还是比较清晰的,每次执行将所有可能的无重复子串列出,长度最长的子串即为我们最终结果。

提交结果执行用时内存消耗语言
通过452 ms47.6 MBJavaScript

双重for循环逐一枚举无疑是特别消耗时间的。

题解2: 滑动窗

思路1

滑动窗就是利用队列的思想,将队列的元素与模板元素进行比较,如果队列中没有目标元素,就将目标元素填充到队列中。如果有目标元素,就不继续滑动窗口,而是将队列元素返回。

  1. 创建一个Set对象用于存放无重复字符的子串,Set是唯一值的集合,所有此处更加贴合。声明max存放最大子串长度。
  2. 如果s长度为0或1,直接返回len。
  3. 循环字符串s,如果set中没有就新增,并更新max;如果有,也就是不符合子串的要求了,那么将set以前存入的重复的元素删除,并将当前的元素新增到set。
  4. 返回最大长度max。
var lengthOfLongestSubstring = function(s) {
 let set= new Set();
    let max = 0;
    if(s.length < 2){return s.length;}
    let j=0;
    for(let i=0;i<s.length;i++){
        if(!set.has(s[i])){
            set.add(s[i]);
            max = Math.max(set.size,max)
        }else{
            while(set.has(s[i])){
                set.delete(s[j]);
                j++;
            }
            set.add(s[i]);
        }
    }
    return max;
};

image.png 结合上图,我们很清晰的可以看到,我们实现的是左面的索引逐步收缩,而右侧的索引逐步扩张,将可能的出现在子串中的元素逐步包含进来,每次循环与暂存的最长子串max比较,最终获得到最长子串的长度。

提交结果执行用时内存消耗语言
通过84 ms46 MBJavaScript

思路2

str.indexOf(searchValue, fromIndex)fromIndex位置开始查找元素,如果s字符串中从formIndex位置起查找遍历的元素s[i],查找到的索引小于当前遍历索引i,说明有重复元素;

每次循环中的i相当于子串的终止位置,终止位置-起止位置+1就是新子串的长度。

  1. 声明minIndex为最长子串起始位置,默认为0;声明最长子串长度len默认为0;
  2. 遍历s字符串,如果s字符串从minIndex子串起止位置开始查找,如果前面有与之重复的元素,则更新minIndex为当前查找到位置的下一位。
  3. 如果没有重复,则更新len子串长度(新子串长度为终止位置-起始位置+1与len原本值中取较大数)。

image.png

var lengthOfLongestSubstring = function(s) {
    let minIndex = 0;
    let len =0;
    for(let i=0;i<s.length;i++){
        s.indexOf(s[i],minIndex) < i?
        minIndex = s.indexOf(s[i],minIndex)+1:
        len = Math.max(len,i-minIndex+1)
    }
    return len
}
提交结果执行用时内存消耗语言
通过80 ms43.7 MBJavaScript