[LeetCode]3. 无重复字符的最长子串(Longest Substring Without Repeating Characters)题解

博客详细解析了LeetCode中3. Longest Substring Without Repeating Characters的问题,提供了使用整数数组作为直接访问表的解决方案,并通过举例说明了算法过程,包括变量i、j和ans的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在leetcode中该题网址:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/

题目描述:

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

示例 1:

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

示例 2:

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

示例 3:

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

官方题解:

当我们知道该字符集比较小的时侯,我们可以用一个整数数组作为直接访问表来替换 Map

常用的表如下所示:

  • int [26] 用于字母 ‘a’ - ‘z’或 ‘A’ - ‘Z’
  • int [128] 用于ASCII码
  • int [256] 用于扩展ASCII码

java代码: 

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        int[] index = new int[128]; // current index of character
        // try to extend the range [i, j]
        for (int j = 0, i = 0; j < n; j++) {
            i = Math.max(index[s.charAt(j)], i);
            ans = Math.max(ans, j - i + 1);
            index[s.charAt(j)] = j + 1;
        }
        return ans;
    }
}

(这个代码是真的简洁啊,就是不够优雅,没有就是,答案赛高!(破音)) 

这是官方题解中的最后一个(我管这个叫做“一个萝卜一个坑”算法,可是这到底该叫作啥啊!!!),官方题解中前面的题解分别是暴力法和滑动窗口法(我看懂了就不整理了)。自己写完之后看不懂这个到底是什么意思,通过一步一步的debug之后终于看懂,整理思路

这个算法是把输入的每一个字符都看成其对应的编码(如ASCII码)。这种看待字符的方法在编程语言中可以十分方便的实现。

如C语言:

int main(){
    char a = 'a';
    printf("%d",a);
    return 0;
}
//这个函数的输出是"97"
//97是'a'的ASCII码值
//还有其他方案可以输出某字符的ASCII码值,不再赘述

输入字符串以 "abcac" 为例,其中包含三个不同的字符‘a',’b',‘c'。分别对应ASCII码为97,98,99

先定义一个index数组,数组长度为128(对应128个ASCII码值)

在for循环中来一步一步地进行以便于理解(嫌麻烦可以直接看总结)

 step1:

执行语句 i = Math.max(index[s.charAt(j)], i);  ans = Math.max(ans, j - i + 1); 后

i 不变,ans 变成1

执行语句 index[s.charAt(j)] = j + 1; 后index数组发生改变,

 

 

step2:

 

执行语句 i = Math.max(index[s.charAt(j)], i);  ans = Math.max(ans, j - i + 1); 后

i 不变, ans 变成2

执行语句 index[s.charAt(j)] = j + 1; 后index数组发生改变,

i 与 j 配合,通过 ans = Math.max(ans, j - i + 1) 语句中的 j - i + 1 和 ans 比较来确定至今的最大不重复子串的长度,而index可以保证 i,j 准确 

 

step3:

 

执行语句 i = Math.max(index[s.charAt(j)], i);  ans = Math.max(ans, j - i + 1); 后

i 不变, ans 变成3

执行语句 index[s.charAt(j)] = j + 1; 后index数组发生改变,

到这一次为止,index中的a,b,c对应的值为1,2,3

从第1次到第3次循环都是类似的

从这三次循环中看出,当遍历到的字符是第一次出现时,i 不会改变,一直是 0,而 j 会在每次循环都自增一次

 

 step4:

 

执行语句 i = Math.max(index[s.charAt(j)], i);  ans = Math.max(ans, j - i + 1); 后

由上表可知:i 会变成 1,ans 依然是3(由于 i 与 j 在这一次循环中同步增加了1,j - i + 1 为3)

执行语句 index[s.charAt(j)] = j + 1; 后index数组发生改变,

这一次中,b,c,a在index中的值分别是2,3,4,刚好是遍历到此处时的最后一个不重复的最长子串"bca"(但这是因为新的字符刚好是之前找到的不重复子串的第一个字符,那如果不是这样呢,比如下一个是'c'呢。见step5)

 

step5:

执行语句 i = Math.max(index[s.charAt(j)], i);  ans = Math.max(ans, j - i + 1); 后

i 变成 3,ans 依然是 3(尽管此时的 j - i + 1 是2)

执行语句 index[s.charAt(j)] = j + 1; 后index数组发生改变,

好吧,勉勉强强还算是有上一步提到的那个规律,毕竟"ac",对应4,5,'b'已经不在以'c'为结尾往前数的不重复子串里了。到这里,每个新遍历到的字符应该会有的情况都有了。

总结

主要的思想是把所给字符串从前到后遍历一遍,每个字符都找到由该字符作为末尾的无重复子串,其长度与ans比较,取其较大者,最后返回ans

几个变量的作用如下:

i:若当前字符是第一次出现,由于index[s.charAt(j)] = 0,故 i 保持不变。

    若当前字符出现过,则 i 是当前字符上一次出现时的 j + 1 即 index[s.charAt(j)] 或者是保持不变(视情况而定),并且会在 ans的赋值语句中被此时的 j + 1 减去,得到该字符作为末尾的无重复子串的长度,与 ans 比较,选较大者赋值给 ans,可得到结果

( i 这个变量的作用可能有一些不对,不过我说不出来。麻烦大佬们改正一下)

j:作为计数器,记录目前字符在所给字符串中的位置

ans:返回值,函数执行过程中应该的最后结果保存在这里。由于在循环中 j 会保持自增,而 i 的变化方式已经说过,所以 ans 会在 ans = Math.max(ans, j - i + 1) 语句的作用下发生改变。

index[]:记录对应字符的最近一次出现在所给字符串中的位置

不行了!!!脑阔疼!!!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值