在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[]:记录对应字符的最近一次出现在所给字符串中的位置
不行了!!!脑阔疼!!!