力扣(LeetCode)100题:3.无重复字符的最长子串
3.无重复字符的最长子串
我的题解:刚开始是暴力但是超时了就改用滑动窗口
class Solution:def lengthOfLongestSubstring(self, s: str) -> int:# 哈希集合,记录每个字符是否出现过occ = set()n = len(s)# 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动rk, ans = -1, 0for i in range(n):if i != 0:# 左指针向右移动一格,移除一个字符occ.remove(s[i - 1])while rk + 1 < n and s[rk + 1] not in occ:# 不断地移动右指针occ.add(s[rk + 1])rk += 1# 第 i 到 rk 个字符是一个极长的无重复字符子串ans = max(ans, rk - i + 1)return ans
这段代码实现了“无重复字符的最长子串”问题的经典解法,采用的是滑动窗口(Sliding Window)策略,配合一个哈希集合(set)来高效判断字符是否重复。
下面逐段解释其逻辑:
occ = set()
n = len(s)
rk, ans = -1, 0
初始化一个空集合 occ,用于记录当前窗口中已出现的字符;n 是字符串长度;rk 表示滑动窗口的右指针,初始为 -1,表示尚未进入字符串;ans 用于记录最长无重复子串的长度,初始为 0。
for i in range(n):
外层循环中的 i 作为滑动窗口的左指针,从 0 遍历到 n-1,表示以每个位置为起点尝试扩展窗口。
if i != 0:occ.remove(s[i - 1])
当 i > 0 时,说明左指针从上一轮的位置向右移动了一位,因此需要将上一轮窗口最左边的字符(即 s[i-1])从集合中移除,确保 occ 始终只包含当前窗口 [i, rk] 内的字符。
while rk + 1 < n and s[rk + 1] not in occ:occ.add(s[rk + 1])rk += 1
在当前左指针 i 固定的情况下,尽可能向右扩展右指针 rk:只要下一个字符 s[rk+1] 不在当前窗口中(即不在 occ 中),就将其加入集合,并将 rk 右移。这个过程会一直持续到遇到重复字符或到达字符串末尾。
此时,窗口 [i, rk] 是以 i 为左端点的最长无重复子串。
ans = max(ans, rk - i + 1)
计算当前窗口的长度 rk - i + 1,并更新全局最大值 ans。
return ans
遍历结束后,返回记录的最大长度。
算法思想总结
该方法维护一个动态滑动窗口 [i, rk],保证窗口内所有字符都不重复。通过两个指针的协同移动:
- 左指针
i每次右移一步,缩小窗口左侧; - 右指针
rk在不产生重复的前提下尽可能右移,扩大窗口右侧。
由于每个字符最多被 rk 访问一次、被 i 移除一次,整个过程的时间复杂度是线性的。
复杂度分析
- 时间复杂度:O(n)。左右指针均最多遍历字符串一次。
- 空间复杂度:O(∣Σ∣),其中 Σ 是字符集大小。在最坏情况下(如所有字符都不重复),集合
occ会存储全部不同字符。对于 ASCII 字符,空间上限为常数(128 或 256),可视为 O(1);若考虑 Unicode,则为 O(n)。
这种方法简洁高效,是解决此类“最长/最短满足条件子串”问题的标准模板。
