【优化版-面试4家12K给了3个offer】LeetCode 热题 100刷题记录-8.无重复字符的最长子串

LeetCode 热题 100刷题记录-8.无重复字符的最长子串

想要在互联网进出离职求职游刃有余,笔试算法题真的必不可少,年轻人听句劝!关注这个系列,每天更新。

记录解题过程,便于个人复盘和追踪进度。分为基础信息、题目分析、解题代码、复盘总结四部分。


题目基础信息

题目名称:无重复字符的最长子串
题目链接无重复字符的最长子串
难度等级:中等
标签分类:数组
完成日期:25.8.20
题目:给定一个字符串 s ,请你找出其中不含有重复字符最长 子串长度

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

**人话版:**一个字符串, 找出里面长度最长的不重复子串,这个 子串的长度


题目分析

核心问题
(用1-2句话概括题目本质)
找出长度最长的不重复子串的长度是多少。

关键约束

初始思路
我有想法,我想到了要创建一个集合,如果里面不存在,就放进去。我也想到了如果当前这个元素和前一个元素重复了,就说明得把里面的删除了,重新开始往里加。但是我就是做不出来。因为我一直在死磕,具体的最长子序列是什么,而不是有多长。然后返回这个集合的长度就可以了,结果理想很美好,现实很残酷。(代码如下第一版 — 全是问题啊bro)

优化方向

  • 妙极了,当我把思维聚集在长度,而不是整个子串是什么的时候就开始好起来了
  • 具体是碰到重复的就将头部一个删除,一直删到不重复为止,每次把最大值更新
  • 原始数组:abcabcbb , -> 集合变化:abc -> bca -> cab -> abc -> bc -> c -> cb -> b
  • 虽然说整个集合最后只剩一个b在里头, 但是最长子串的长度max早已被我记录
  • 上面这个就是算法的核心。具体见详细的注释和代码。

输入: “abcabcbb”
初始:[a],max = 1
加入 b → [a,b],max = 2
加入 c → [a,b,c],max = 3
下一个 a 已存在 → 移除左边 a → [b,c]
再次检查 a → 加入 → [b,c,a],max = 3
继续往后走……最后结果 3


解题代码

# 示例:最长子序列 拉跨版 只能通过小部分测例
public int lengthOfLongestSubstring(String s) {
    // 将字符串转为字符数组,方便逐个处理
    char[] chars = s.toCharArray();
    
    // 特殊情况:如果字符串长度为 0,直接返回 0
    if (0 == chars.length) {
        return 0;
    }

    // 用 HashSet 来存储当前子串的字符,保证无重复
    HashSet<Character> temp = new HashSet<>();
    
    // 先把第一个字符放入集合
    temp.add(chars[0]);

    // 从第二个字符开始遍历
    for (int i = 1; i < chars.length; i++) {
        // 如果当前字符和前一个字符相同,
        // 并且集合大小 < 剩余未遍历的字符数,
        // 就清空集合(重新开始记录)
        // ❌ 这一段逻辑其实有问题:
        // 无重复子串的判断不是只看相邻字符是否相等。
        if (chars[i] == chars[i - 1] && temp.size() < chars.length - i) {
            temp.clear();
        }

        // 如果当前字符没有出现在集合中,就加入集合
        if (!temp.contains(chars[i])) {
            temp.add(chars[i]);
        }
    }

    // 返回集合的大小(表示最后得到的无重复字符子串长度)
    // ❌ 但注意:这样只能得到“最后一次集合”的大小,不一定是最长的子串
    return temp.size();
}

# 示例:最长子序列 通过版   好理解
public int lengthOfLongestSubstring(String s) {
    // 将字符串转换成字符数组,方便逐个处理
    char[] chars = s.toCharArray();

    // 定义最大长度,初始值为 1(因为至少有一个字符)
    int max = 1;

    // 特殊情况:如果字符串长度为 0,直接返回 0
    if (0 == chars.length) {
        return 0;
    }

    // 用 ArrayList 来存储当前窗口中的字符(窗口保证无重复)
    //而且这里一定不能用 hashSet, 因为它会自动排序。
    ArrayList<Character> temp= new ArrayList<>();

    // 先把第一个字符加入窗口
    temp.add(chars[0]);

    // 从第二个字符开始遍历
    for (int i = 1; i < chars.length; ) {

        // 如果当前字符没有在窗口中出现
        if (!temp.contains(chars[i])) {
            // 将该字符加入窗口
            temp.add(chars[i]);

            // 移动右指针 i,继续处理下一个字符
            i++;

            // 更新最长子串长度
            max = Math.max(max, temp.size());
        } else {
            // 如果当前字符已经在窗口中出现(发生重复)
            // 就从窗口左边移除一个字符,相当于滑动窗口的左指针右移一位
            temp.remove(0);

            // 注意这里没有移动 i,因为要继续检查当前的 chars[i]
            // 直到重复字符被移除,才能把它加进去
        }
    }

    // 返回最长子串的长度
    return max;
}


复盘总结

易错点
1.以下是代码语义版思路:
特判

  • 把字符串转成字符数组。
  • 如果长度为 0,直接返回 0。

初始化

  • max = 1(至少有一个字符时,答案不会小于 1)。
  • 新建一个“窗口”列表,用来装当前不含重复字符的子串。
  • 先把第一个字符放进窗口。
  • 右指针 i 从第二个字符的位置开始(i = 1)。

主循环(当 i < 字符串长度 时重复执行)

  • 看第 i 个字符是否已经在窗口里:

情况一:不在窗口里

  • 把它加入窗口;
  • i 右移一位(因为这个字符已被成功纳入窗口);
  • 用当前窗口大小更新 maxmax = max(max, 窗口大小))。

情况二:在窗口里(发生重复)

  • 从窗口左侧移除一个字符(即移除当前窗口的第一个元素);
  • 注意:此时不移动 i,因为还没把第 i 个字符成功放进来;
  • 这样做的含义是:不断收缩左边界,直到把与第 i 个字符相同的旧字符移出窗口,再尝试把第 i 个字符放进来。

循环结束与返回

  • 当右指针 i 走到字符串末尾(i == 长度),说明所有字符都处理完;
  • 返回记录到的 max,它就是“无重复字符的最长子串”的长度。

2.这个temp临时集合一定不能是HashSet,因为HashSet会自动排序,会导致进去的元素,失去原来的顺序,导致算法失效

3.这个题目一定要想到记录最大值啊,这个值才是返回的答案!! 和什么字符串序列是什么无所谓。


复杂度分析

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

相似题目

  • 指针类、hash类

进阶思考


扩展建议

提示:可配合GitHub仓库同步代码,利用提交记录跟踪进步轨迹。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值