Leecode 0003. Longest Substring Without Repeating Characters

3. Longest Substring Without Repeating Characters
题目
Given a string, find the length of the longest substring without repeating characters.
Example 1:
1 | Input: "abcabcbb" |
Example 2:
1 | Input: "bbbbb" |
Example 3:
1 | Input: "pwwkew" |
题目大意
在一个字符串中寻找没有重复字母的最长子串。
解题思路
这一题和第 438 题,第 3 题,第 76 题,第 567 题类似,用的思想都是"滑动窗口"。
滑动窗口的右边界不断的右移,只要没有重复的字符,就持续向右扩大窗口边界。一旦出现了重复字符,就需要缩小左边界,直到重复的字符移出了左边界,然后继续移动滑动窗口的右边界。以此类推,每次移动需要计算当前长度,并判断是否需要更新最大长度,最终最大的值就是题目中的所求。
方法一:基础滑动窗口(暴力收缩左边界)
思路
用一个数组cnt[128]记录窗口内每个字符出现的次数,数组大小为 128 是为了覆盖所有 ASCII 字符。
右指针right从 0 开始遍历字符串,每遍历到一个字符,就将其在cnt数组中的计数加 1。
当cnt[s[right]] > 1时,说明出现了重复字符,此时左指针left向右移动,同时将对应的字符计数减 1,直到cnt[s[right]] == 1,确保窗口内没有重复字符。
每次调整窗口后,计算当前窗口长度right - left + 1,并与最大长度比较,更新最大长度。
复杂度分析
时间复杂度:O (n),其中 n 是字符串的长度。虽然存在嵌套的 while 循环,但每个字符最多被左指针和右指针各访问一次。
空间复杂度:O (1),因为数组cnt的大小是固定的 128,不随输入字符串的长度变化而变化。
方法二:优化滑动窗口(直接定位左边界)
思路
- 基础方法中,左边界是逐步收缩的,而优化方法则是直接定位到合适的左边界位置,减少不必要的移动。
- 用一个数组last_pos[128]记录每个字符最后一次出现的索引,初始值设为 - 1,表示该字符尚未出现过。
- 右指针right遍历字符串,对于当前字符c,如果last_pos[c] >= left,说明该字符在当前窗口内已经出现过,此时直接将左边界left更新为last_pos[c] + 1,跳过中间不必要的收缩过程。
- 更新last_pos[c]为当前的right值,并计算当前窗口长度,更新最大长度。
复杂度分析
- 时间复杂度:相比基础方法,该方法减少了左指针的移动次数,将左指针的 “逐步移动” 改为 “直接跳转”,在实际运行中效率更高。时间复杂度仍为 O (n),空间复杂度为 O (1),但实际性能更优。
方法三:针对特定字符集的进一步优化
思路
- 如果已知输入字符串的字符集范围较小,比如仅包含小写字母,那么可以进一步优化空间使用。
- 对于仅包含小写字母的字符串,使用大小为 26 的数组来记录字符信息,通过c - 'a'将字符映射为 0-25 的索引。
- 其他逻辑与优化滑动窗口方法一致。
优化点
- 该方法适用于明确知道输入字符集范围的情况,能有效节省内存空间,但通用性相对较差。如果输入字符串可能包含其他字符(如大写字母、数字、符号等),则不适用。
代码实现
1 | int lengthOfLongestSubstring(char* s) { |
1 |
|
1 |
|
1 | #define MAX(a, b) ((b) > (a) ? (b) : (a)) |
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.