Leetcode 159 无重复字符的最长子串 | 长度最小的子数组

1 题目

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

提示

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

示例 1:

复制代码
输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。注意 "bca" 和 "cab" 也是正确答案。

示例 2:

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

示例 3:

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

提示:

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

2 代码实现

c++

cpp 复制代码
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int left = 0 ; 
        int right = 0 ;
        int maxLen = 0 ; 
        unordered_map <char , int > res ;

        while(right < s.size() ){
            char cur = s[right];

            if (res.find(cur) == res.end() || res[cur] < left){
                res[cur] = right ;
                maxLen = max(maxLen , right - left + 1 );
                right ++;
            }else {
                left = res[cur] + 1 ;
            }
        }
        return maxLen ;
    }
};

js

javascript 复制代码
/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    let maxLen = 0 ; 
    let left = 0 ;
    let right = 0 ;
    const map = new Map();
    while (right < s.length){
        const c = s[right];

        if (!map.has(c) || map.get(c) < left ){
            map.set(c, right);
            maxLen = Math.max(maxLen , right - left + 1 );
            right ++ ;
        }else {
            left = map.get(c) + 1 ; 
        }
    }

    return maxLen ;
};

思考

不知道应该怎么去维护这个...这个和滑窗的关系是?。。。因为子串的定义是要连续的,所以这里滑窗的作用是为了是连续的字串,是否有重复呢,那就用哈希表来维护一下,比如是否在表里面存过,c++里面是unordered_map , js里面是map,但其是具体的api还不知。。。。。

自己乱写了一通

cpp 复制代码
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int left = 0 ; 
        int right = s.size() - 1 ;
        unordered_map <int , int > res ;

        while (left <=  right){
            if ()//没见过的,而且没重复,加进来 ; else 左边界收缩

            //前进的状态一直是右边界往前走,左边界依据是否见过而收缩
            //同时要维护一个最终的答案 len ,是cur还是之前规定好的len ,采用max函数

        }
    }
};

题目描述

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

提示:0 ≤ s.length ≤ 5 * 10⁴,s 由英文字母、数字、符号和空格组成。

示例

  • 示例 1:输入 s = "abcabcbb",输出 3(最长子串为 "abc"、"bca" 等);

  • 示例 2:输入 s = "bbbbb",输出 1(最长子串为 "b");

  • 示例 3:输入 s = "pwwkew",输出 3(最长子串为 "wke",注意 "pwke" 是子序列而非子串)。

解题思路(滑动窗口 + 哈希表)

核心思路与你最初的想法完全一致:用滑动窗口保证子串的连续性,用哈希表判断窗口内是否有重复字符,具体逻辑如下:

  1. 滑动窗口定义:用 left(左边界)和 right(右边界)表示当前无重复字符的连续子串,窗口范围为 [left, right];

  2. 右指针移动:right 从 0 开始,依次遍历字符串的每个字符,负责"扩大窗口";

  3. 哈希表作用:记录每个字符最后一次出现的下标(重点解决你疑惑的 key/value 问题);

  4. 左指针移动:当遇到重复字符时,将 left 跳到重复字符的下一位,负责"收缩窗口",保证窗口内始终无重复;

  5. 记录最大值:每次扩大或调整窗口后,计算窗口长度,用 max 函数保留最长长度。

代码实现

cpp 复制代码
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int left = 0;         // 滑动窗口左边界(你最初定义的变量,保留不变)
        int right = 0;        // 修正:右边界从0开始遍历,而非s.size()-1
        int maxLen = 0;       // 维护最终答案:最长无重复子串的长度
        // 哈希表:key=字符(char),value=该字符最后出现的下标(int)(解决你疑惑的key/value)
        unordered_map<char, int> res;

        // 你最初写的while循环,逻辑完全保留,仅补全内部判断
        while (right < s.size()) {
            char c = s[right];  // 当前遍历到的字符(c就是哈希表的key)

            // 核心判断(重点讲解,解决你的疑惑)
            // 条件含义:当前字符没出现过,或者出现过但不在当前窗口内 → 无重复
            if (res.find(c) == res.end() || res[c] < left) {
                // 重点:res[c] = right → key是字符c,value是当前字符的下标right
                // 作用:记录当前字符的最新位置,方便后续判断是否重复
                res[c] = right;
                // 计算当前窗口长度(right - left + 1),更新最长长度
                maxLen = max(maxLen, right - left + 1);
                // 无重复 → 右指针继续右移,扩大窗口(符合你"右边界一直往前走"的思路)
                right++;
            } else {
                // 否则:当前字符在窗口内重复了 → 左边界收缩(你最初的思路)
                // res[c]是该字符上次出现的下标,left跳到它的下一位,保证窗口无重复
                left = res[c] + 1;
            }
        }

        return maxLen;
    }
};

关键代码详解(针对你的疑惑点)

1. 哈希表 res 的 key 和 value(res[c] = right)

你疑惑的 res[c] = right; 是整个题解的核心,拆解如下:

  • res 是 unordered_map<char, int> 类型,规定了 key 必须是字符(char),value 必须是整数(int)

  • c 是当前遍历到的字符(比如 s = "abc",right=0 时,c = 'a'),所以 key = c(当前字符)

  • right 是当前字符 c 在字符串 s 中的下标(比如 c='a' 时,right=0),所以 value = right(当前字符的下标)

  • 这句话的作用:把"当前字符"和"它的位置"存进哈希表,方便后续遇到重复字符时,快速找到它上次出现的位置。

2. 核心判断条件(res.find(c) == res.end() || res[c] < left)

这句话是判断"当前字符是否在窗口内重复"的关键,拆成两部分讲,全程大白话:

  • 第一部分:res.find(c) == res.end()

    • res.find(c):在哈希表中查找当前字符 c;

    • res.end():哈希表的"末尾",表示"没找到";

    • 整体含义:当前字符 c 从来没出现过,肯定不重复,可以加入窗口。

  • 第二部分:res[c] < left

    • res[c]:通过 key(字符 c)找到 value(c 上次出现的下标);

    • left:当前滑动窗口的左边界;

    • 整体含义:字符 c 以前出现过,但它上次出现的位置在当前窗口左边(不在窗口内),不算重复,也可以加入窗口。

总结:两个条件满足任意一个,就说明当前字符无重复,可扩大窗口;两个条件都不满足,说明字符在窗口内重复,需要收缩左边界。

复杂度分析

  • 时间复杂度:O(n),n 是字符串 s 的长度,每个字符只被 right 遍历一次,left 只移动不回溯,整体遍历一次即可;

  • 空间复杂度:O(min(m, n)),m 是字符集的大小(英文字母、数字、符号等的总数量),哈希表最多存储 m 个不同字符,最坏情况下存储 n 个字符(所有字符都不重复)。

示例演示(帮助理解窗口移动)

以 s = "abcabcbb" 为例,简化演示关键步骤,重点看哈希表和窗口变化:

  1. right=0(c='a'):res 中无 'a' → 存 res['a']=0,窗口 [0,0],maxLen=1,right++;

  2. right=1(c='b'):res 中无 'b' → 存 res['b']=1,窗口 [0,1],maxLen=2,right++;

  3. right=2(c='c'):res 中无 'c' → 存 res['c']=2,窗口 [0,2],maxLen=3,right++;

  4. right=3(c='a'):res['a']=0 ≥ left=0(在窗口内)→ left=0+1=1,不移动 right;

  5. right=3(c='a'):再次判断,res['a']=0 < left=1(不在窗口内)→ 存 res['a']=3,窗口 [1,3],maxLen 仍为3,right++;

  6. 后续继续遍历,窗口始终维护无重复,最终 maxLen=3,与示例输出一致。

总结

本题核心是"滑动窗口 + 哈希表",你最初的代码框架完全正确,仅需补充3个关键细节:

  1. 右指针 right 从 0 开始遍历,而非字符串末尾;

  2. 哈希表 res 的 key 是字符、value 是下标,用 res[c] = right 记录字符位置;

  3. 用 res.find(c) == res.end() || res[c] < left 判断是否重复,重复则收缩左指针。

上述代码可直接提交通过,且完全贴合你的思考逻辑,重点标注了你之前不懂的两个核心点,可对照代码反复理解。

没问题!我1:1 把你这段 C++ 代码翻译成 JavaScript逻辑完全一样结构完全一样变量名几乎一样,你一眼就能对应上!

JavaScript 实现

1. 哈希表

C++:

cpp 复制代码
unordered_map<char, int> res;
res[c] = right;
res.find(c) == res.end();
res[c] < left;

JS:

javascript 复制代码
const map = new Map();
map.set(c, right);
!map.has(c)
map.get(c) < left

2. 核心判断(逻辑 100% 一样)

C++:

cpp 复制代码
if (res.find(c) == res.end() || res[c] < left)

JS:

javascript 复制代码
if (!map.has(c) || map.get(c) < left)

3. 存字符位置

C++:

cpp 复制代码
res[c] = right;

JS:

javascript 复制代码
map.set(c, right);

4. 重复时收缩左指针

C++:

cpp 复制代码
left = res[c] + 1;

JS:

javascript 复制代码
left = map.get(c) + 1;

1. key 和 value 是谁?

javascript 复制代码
map.set(c, right)
key   = c(字符)
value = right(下标)

和你 C++ 的 res[c] = right 完全一样

2. 判断条件什么意思?

javascript 复制代码
!map.has(c)       // 从没出现过
||
map.get(c) < left // 出现过,但不在窗口里
  • 双指针滑动窗口
  • 右指针一直走
  • 哈希表存字符位置
  • 重复就跳左指针
  • 维护最大长度

3 题目

209. 长度最小的子数组

给定一个含有 n个正整数的数组和一个正整数 target

找出该数组中满足其总和大于等于target的长度最小的 子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度**。** 如果不存在符合条件的子数组,返回 0 。(这里的l,r都是下标)

示例 1:

复制代码
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

复制代码
输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

复制代码
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:

  • 1 <= target <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 104

进阶:

  • 如果你已经实现O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。

4 代码实现

c++

cpp 复制代码
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int minLen = INT_MAX ; 
        int left = 0 ;
        int right = 0 ;
        int sum = 0 ;

        while (right < nums.size()){
            sum += nums[right];

            while(sum >= target){
                minLen = min(minLen , right - left + 1 );
                sum -= nums[left];
                left ++ ;
            }
            right ++ ;
        }
        return minLen == INT_MAX ? 0 : minLen ;
    }
};

js

javascript 复制代码
/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function(target, nums) {
    let minLen = Infinity ;
    let left = 0 ;
    let right = 0 ;
    let sum = 0 ;

    while (right < nums.length){
        sum += nums[right];
            while(sum >= target){
                minLen = Math.min(right - left + 1 , minLen);
                sum -= nums[left];
                left ++ ;
            }
        right ++ ;
    }
    return minLen === Infinity ? 0 : minLen ; 
    
};

思考

蛤,毫无头绪啊,这怎么搞的啊????

1.base case 遍历所有,sum看看是否小于 , 那就是0 ; 好像没这个必要。。

  1. 先排序吗,然后长度最小肯定是要从最大数字往最小数字走,这样维护一个滑窗?不是的!!!

因为题目要的是 连续子数组排序会破坏连续性,直接错误!!!那就是正常滑窗就ok了

我服了我写不出来

cpp 复制代码
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int minLen = 0 ; 
        int left = 0 ;
        int right = 0 ;
        int sum = 0 ;

        while (right < nums.size() && left <= right ){
            if (sum < target){
                right ++;
                sum += nums[right];
            }else{
                left ++;
                sum -= nums[left];
            }
            minLen = min(right - left + 1 , minLen);
        }
        return minLen ;
    }
};

题解

1. 为什么不能先排序?

你自己已经悟了:题目要连续子数组,排序会破坏顺序 → 直接错!

2. 滑动窗口核心(和上一题一模一样)

  • right 一直往前走
  • 把数字加入窗口 sum
  • 一旦 sum ≥ target尝试缩 left,让窗口尽量短
  • 每次缩的时候,更新最短长度

3. 你代码的 3 个小错误(超级好改)

  1. minLen 初始不能是 0 应该是无穷大 INT_MAX

  2. 不能先移动指针,再加减必须:

    cpp 复制代码
    sum += nums[right]
    right++

    你写反了

  3. 更新最小长度必须在 sum >= target 里面不是每次循环都更新


最关键的:为什么用 while 不是 if?

因为:加完一个数可能满足 sum >= target 缩 left 一次后可能 still >= target 还能再缩!

所以必须用 while 缩到不能缩为止。


你能记住的终极口诀(两道题通用)

cpp 复制代码
right 一直走
加入窗口
while(满足条件) {
    更新答案
    缩 left
}

上一题满足条件:无重复 这一题满足条件:sum >= target

cpp 复制代码
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int left = 0;         // 完全保留:滑动窗口左边界
        int right = 0;        // 完全保留:右边界从0开始
        int minLen = INT_MAX;  // 最小改动:maxLen → minLen,初始无穷大

        // 最小改动:去掉哈希表,加一个 sum 记录窗口和
        int sum = 0;

        // 完全保留:你最爱的 while 循环!
        while (right < nums.size()) {
            
            // 最小改动:把当前数字加入窗口 sum
            sum += nums[right];

            // ==================== 最小改动核心 ====================
            // 把你的"字符不重复判断" 换成 "sum >= target"
            // 并且用 while 缩到不能缩为止
            while (sum >= target) {
                // 最小改动:更新最小长度
                minLen = min(minLen, right - left + 1);
                // 最小改动:缩左指针,减去左边数字
                sum -= nums[left];
                left++;
            }
            // ======================================================

            // 完全保留:右边界一直往前走
            right++;
        }

        // 最小改动:找不到返回 0
        return minLen == INT_MAX ? 0 : minLen;
    }
};

5 小结

滑窗这么简单太久没做也还是会忘记,特别要注意 js 的语法,特别要注意这个!!!

相关推荐
天才熊猫君2 小时前
通用 Loading 状态管理器
前端·javascript·vue.js
三品吉他手会点灯2 小时前
C语言学习笔记 - 2.C概述 - HelloWorld程序举例
c语言·笔记·学习
雪芽蓝域zzs2 小时前
uni-app x 使用 UTS 语言使用 mixins
开发语言·javascript·uni-app
Rousson2 小时前
硬件学习笔记-97 不同存储器件简单介绍
笔记·学习
浮芷.2 小时前
微观搜打撤:基于鸿蒙flutter的内存快照算法的局内外状态隔离与高阶背包系统设计
算法·flutter·华为·开源·harmonyos·鸿蒙
郝学胜-神的一滴2 小时前
[力扣 105]二叉树前中后序遍历精讲:原理、实现与二叉树还原
数据结构·c++·算法·leetcode·职场和发展
sheeta19982 小时前
LeetCode 每日一题笔记 日期:2026.04.20 题目:2078.两栋颜色不同而距离最远的房子
笔记·算法·leetcode
DaqunChen2 小时前
全栈开发的演变:从LAMP到MEAN再到现代JavaScript
开发语言·javascript·ecmascript
闻缺陷则喜何志丹2 小时前
【ST表 前缀和】P7809 [JRKSJ R2] 01 序列|普及+
c++·算法·前缀和·洛谷·st表