2026-01-11 14:52:18
给定一个字符串 s,找出其中不含有重复字符的最长子串的长度。
示例分析示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 无重复字符的最长子串是 "abc",长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 无重复字符的最长子串是 "b",长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 无重复字符的最长子串是 "wke",长度为 3。注意 "pwke" 是子序列,不是子串。
要解决这个问题,我们需要找到字符串中最长的无重复字符的子串。可以使用滑动窗口(Sliding Window)的方法来高效地解决这个问题。滑动窗口是一种常用的处理字符串或数组子区间问题的技术,通过维护一个窗口(通常由两个指针表示)来动态调整子区间的范围。
滑动窗口的基本思想:提供的代码中有两种实现方法,主要区别在于如何处理重复字符时 start 指针的更新。
方法一:def lengthOfLongestSubstring1(self, s: str) -> int: start = 0 end = 0 ditt = {} res = 0 while end < len(s): if s[end] in ditt.keys(): start = max(start, ditt[s[end]]) res = max(res, end - start + 1) ditt[s[end]] = end + 1 end += 1 return res当遇到重复字符时,start 更新为 max(start, ditt[s[end]])。
ditt[s[end]] 存储的是 end + 1,即重复字符的下一个位置。
这种更新方式可能会导致 start 指针回退。例如,对于字符串 "abba",当 end 指向第二个 'a' 时,start 会被更新为 ditt['a'] = 1,但实际上 start 已经移动到 2(因为 'b' 重复),因此 start 不应该回退。
因此,这种方法在某些情况下可能无法正确计算最长子串。
当遇到重复字符时,start 更新为 max(start, ditt[s[end]] + 1)。
ditt[s[end]] 存储的是 end,即重复字符的当前位置。
这种方法确保 start 指针不会回退,因为 ditt[s[end]] + 1 是重复字符的下一个位置,而 start 已经可能更大(例如之前因为其他字符重复而移动过)。
因此,这种方法是正确的。
方法二是正确的实现。以下是其详细解释:
如果 s[end] 在 ditt 中,说明字符重复,更新 start 为 max(start, ditt[s[end]] + 1)。
更新 res 为当前窗口长度 end - start + 1 和 res 的较大值。
将 s[end] 的位置更新为 end(即 ditt[s[end]] = end)。
移动 end 指针。
以 s = "abba" 为例:
'a' 不在 ditt 中。
res = max(0, 0 - 0 + 1) = 1。
ditt = {'a': 0}。
end = 1。
'b' 不在 ditt 中。
res = max(1, 1 - 0 + 1) = 2。
ditt = {'a': 0, 'b': 1}。
end = 2。
'b' 在 ditt 中,ditt['b'] = 1。
start = max(0, 1 + 1) = 2。
res = max(2, 2 - 2 + 1) = 2。
ditt = {'a': 0, 'b': 2}。
end = 3。
'a' 在 ditt 中,ditt['a'] = 0。
start = max(2, 0 + 1) = 2。
res = max(2, 3 - 2 + 1) = 2。
ditt = {'a': 3, 'b': 2}。
end = 4。
以下是正确的实现代码:
class Solution: def lengthOfLongestSubstring(self, s: str) -> int: start = 0 end = 0 ditt = {} res = 0 while end < len(s): if s[end] in ditt.keys(): start = max(start, ditt[s[end]] + 1) res = max(res, end - start + 1) ditt[s[end]] = end end += 1 return res复杂度分析