3. 无重复字符的最长子串
3 无重复字符的最长子串
题目链接:3. 无重复字符的最长子串
核心思考:如何寻找最长子串?
对于寻找“无重复字符”的最长子串,最直观的想法就是穷举:也就是暴力遍历。
尝试从每一个字符开始,向后看能走多远而不遇到重复的字符。但这会带来 $O(n^2)$ 的时间复杂度。
我们可以优化这个过程。在向后扫描的过程中,当遇到一个已经出现过的字符时,前面已经建立好的“无重复状态”被打破了。此时我们不需要重新从头再来寻找,而是可以通过滑动窗口的机制,只剔除掉造成重复的部分。
解题思路
方法一:暴力遍历 (Brute Force)
暴力的思路非常简单,使用两个嵌套循环。外部循环固定子串的起始位置,内部循环向后扩展并检查是否遇到重复字符。
局限性:这种方法时间复杂度为 $O(n^2)$,在字符串较长时非常低效。
1 | class Solution: |
方法二:滑动窗口 (Sliding Window)
破局点: 将子串理解成一个“小窗口”在移动,一直框选着符合条件的内容。
我们可以使用左右两个指针(left 和 i)来维护这个窗口。当右指针向右扩展遇到重复字符时,我们不需要让右指针回退,只需要让左指针向右收缩,直到把那个重复的字符“踢出”窗口即可。
算法步骤
- 初始化:左指针
left = 0,使用一个 Set (lookup) 来记录窗口内当前的字符集合。 - 移动右指针:将第一个元素添加进窗口,遍历字符串的每个字符被视为右指针的移动。
- 收缩窗口:如果发现右指针将要加入的字符已经存在于
lookup中,则通过while循环不断将左指针left右移,并将移出窗口的最左侧字符从lookup中删除,直到满足条件。 - 更新结果:每次成功加入新字符后,记录目前的长度并和最大长度比较。
1 | class Solution: |
精妙之处:安全剪枝
这就是滑动窗口解决这类问题的高效所在,它避免了暴力解法中的重复计算。外部的for循环明确执行 $n$ 次,关键在于内部的while循环:虽然它看起来可能在最坏情况下对每个字符都执行多次,但实际上,左指针left和扫描的索引i都只会单向向前移动。字符串中的每个字符最多只会被处理两次(一次进入窗口,一次由于左指针收缩被移出窗口),将时间复杂度从 $O(n^2)$ 优雅地降到了 $O(n)$。
复杂度分析
- 时间复杂度分析:$O(n)$,其中 $n$ 是字符串的长度。左指针最多移动 $n$ 次,右指针遍历 $n$ 次,总执行次数受限于字符串长度。
- 空间复杂度分析:$O(\min(m, n))$,其中 $m$ 是字符集的大小。因为在最坏情况下,
lookup集合最多包含字符集大小的字符数。
C++ 实现与 STL 容器扩充
在 C++ 中,类似的逻辑可以通过引入 <unordered_set> 容器来实现(使用哈希表保证 $O(1)$ 的查找)。对于这道题使用这种容器几乎是唯一解法,不使用 STL 会使其极度复杂(你需要自己实现类似哈希表的数据结构)。
1 |
|
注:insert 方法用于向集合中添加元素,erase 方法用于删除指定元素,而 find 结合 end 返回值则用于检查元素是否存在。