LeetCode 3. 无重复字符的最长子串 | 算法-从菜鸟开始

简介: 本文是《算法-从菜鸟开始》系列文章的第4篇,欢迎收藏、留言、点赞。话不多说,上题!

一、LeetCode 3. 无重复字符的最长子串


题目介绍:


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


示例:


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


注意:这道题目让输出的是不含有重复字符的最长子串的长度,而不是子串本身


二、解题思路


  1. 第一种解法:”老实人“又要上线了


题目中是要找不含有重复字符的子串,返回其长度。


第一时间我们可以想到找出所有的符合条件的子串,然后比较所有子串的长度,取最大的即可。


接下来就是考虑排列组合的问题。


在示例中,输入的是字符串”abca“,可以组成的排列组合有:


// 字符串abca可以组成的排列组合子串
// 字符a开始
ab | abc | abca
// 字符b开始
bc | bca
// 字符c开始
ca


再次筛选不包含重复字符串的:


ab | abc | bc | bca | ca


由此可得到结果,不含有重复字符的最长子串的长度是3


/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
  // 记录最大子串长度
  let maxLen = 0;
  // 当前子串
  let currentSubStr = '';
  // 提供每一种组合
  for (let i = 0; i < s.length; i++) {
    // 每次遍历开始,初始化当前字符
    currentSubStr = s[i];
    // 开始排列组合
    for (let j = i + 1; j < s.length; j++) {
      // 每次判断当前字符是否已经存在于currentSubStr中
      // 如果存在说明当前的currentSubStr子串已经是目前最大的一个
      if (currentSubStr.includes(s[j])) {
        // 将当前子串的长度与历史记录的子串长度进行比较,取二者最大值
        maxLen = Math.max(currentSubStr.length, maxLen);
        // 将当前子串重置为当前字符,再次开始记录
        currentSubStr = s[j];
      } else {
        // 不重复字符,直接累积
        currentSubStr += s[j];
      }
    }
    // 最后一次比对
    maxLen = Math.max(currentSubStr.length, maxLen);
  }
  return maxLen;
}


执行测试用例:


网络异常,图片无法展示
|


OK,没问题我们就提交了!


网络异常,图片无法展示
|


执行结果是:超出时间限制,说明我们这个实现是不理想的,消耗时间太长。


分析下这个算法的时间复杂度: O(n2)O(n^2)O(n2)


在LeetCode的这个Case中,实际传入传入的字符串长度为 31000!


So,这不是一个好算法!


  1. 瞅瞅第二种解法:滑动窗口


滑动窗口是一个什么样的概念呢,大家可以想象为在横向平铺的字符串上,有一个可以滑动的框,框里面选中的是不重复的子串。


输入字符串:abcdbe,在遍历字符串时,我们使用数组subStr存储不重复的子串。
假定我们有两个指针:左指针和右指针,同时指向了不重复子串的首尾两个位置,在初始时都指向了字符串的第一个字符; 开始遍历时,右指针开始挪动,将不重复的字符依次放入到数组中。


如果遇到某个字符已经在数组subStr中,则说明当前不重复子串已经是最长了,需要开启新的不重复子串了。


新的不重复子串从哪里开始记录呢?!


重点: 从该重复字符在不重复子串中的下一个位置开始记录。


在该case中,第一次记录的不重复子串为 abcd。下一个字符b开始重复,新的一个不重复子串开始位置为c,即字符b第一次出现的位置的下一个字符。


此刻的处理方式是移动左指针位置,即将数组subStr中的元素从开头开始进行删除,直到移除重复字符b


将当前的字符b存储到subStr中。


比对当前不重复子串的长度与历史记录的最大长度,取最大值。


重复上述的过程,直到字符串遍历结束。


我们来看下代码上的实现


/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
  // subStr存储不重复子串
  const subStr = [];
  // 记录不重复子串的最大值
  let maxLen = 0;
  // 遍历字符串s
  for (let i = 0; i< s.length; i++) {
    // 判断当前字符s[i]是否已经存在
    while (subStr.includes(s[i])) {
      // 如果存在,则移动指针,将该重复字符第一次出现的位置之前的所有字符清除,重新开始记录一个新的子串
      subStr.shift();
    }
    // 将当前字符存入
    subStr.push(s[i])
    // 比对当前子串的最大长度与历史记录最大长度,取最大值
    maxLen = Math.max(subStr.length, maxLen)
  }
  // 返回最大值
  return maxLen
};


此算法的时间复杂度是O(n)O(n)O(n)


执行代码,测试用例通过,提交算法,看看结果如何。


网络异常,图片无法展示
|


嗯,还是非常理想的。


三、总结


今天我们接触了一个非常重要的概念:滑动窗口,这是一个非常重要的概念和思维方式,希望各位小伙伴都能好好的理解一下,在后面的学习过程中我们还会多次碰到它。

从满足题目要求,到针对性能优化;从时间复杂度O(n2)O(n^2)O(n2)优化到时间复杂度O(n)O(n)O(n),算法总是一个探索、寻求最优解的过程。


除了使用滑动窗口这种方式之外,你还有其他的实现方式吗,欢迎留言交流~


算法-从菜鸟开始,而无止境。与君共勉!



相关文章
|
2天前
|
存储 算法
Leetcode 30天高效刷数据结构和算法 Day1 两数之和 —— 无序数组
给定一个无序整数数组和目标值,找出数组中和为目标值的两个数的下标。要求不重复且可按任意顺序返回。示例:输入nums = [2,7,11,15], target = 9,输出[0,1]。暴力解法时间复杂度O(n²),优化解法利用哈希表实现,时间复杂度O(n)。
22 0
|
2天前
|
算法
代码随想录算法训练营第六十天 | LeetCode 84. 柱状图中最大的矩形
代码随想录算法训练营第六十天 | LeetCode 84. 柱状图中最大的矩形
23 3
|
2天前
|
存储 算法
代码随想录算法训练营第五十九天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
代码随想录算法训练营第五十九天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
22 1
|
2天前
|
算法
代码随想录算法训练营第五十七天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
代码随想录算法训练营第五十七天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
18 3
|
2天前
|
算法
代码随想录算法训练营第五十六天 | LeetCode 647. 回文子串、516. 最长回文子序列、动态规划总结
代码随想录算法训练营第五十六天 | LeetCode 647. 回文子串、516. 最长回文子序列、动态规划总结
34 1
|
2天前
|
算法
代码随想录算法训练营第五十五天 | LeetCode 583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结
代码随想录算法训练营第五十五天 | LeetCode 583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结
24 1
|
2天前
|
算法 API DataX
二叉树(下)+Leetcode每日一题——“数据结构与算法”“对称二叉树”“另一棵树的子树”“二叉树的前中后序遍历”
二叉树(下)+Leetcode每日一题——“数据结构与算法”“对称二叉树”“另一棵树的子树”“二叉树的前中后序遍历”
|
2天前
|
算法 C++
【刷题】Leetcode 1609.奇偶树
这道题是我目前做过最难的题,虽然没有一遍做出来,但是参考大佬的代码,慢慢啃的感觉的真的很好。刷题继续!!!!!!
9 0
|
2天前
|
算法 索引
【刷题】滑动窗口精通 — Leetcode 30. 串联所有单词的子串 | Leetcode 76. 最小覆盖子串
经过这两道题目的书写,相信大家一定深刻认识到了滑动窗口的使用方法!!! 下面请大家继续刷题吧!!!
12 0