1. 理解子串问题的本质
在算法领域,子串问题一直是个高频考点。所谓子串,指的是字符串中连续的字符序列。与子序列不同,子串要求字符必须是连续出现的。举个例子,字符串"abcde"中,"bcd"是子串,而"ace"则是子序列。
注意:子串和子序列的区别是面试中经常被问到的概念,务必牢记子串必须是连续的字符组合。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Hot100中的经典子串问题
LeetCode Hot100题库中包含了多个经典的子串问题,这些题目往往考察我们对滑动窗口、动态规划等算法的掌握程度。最常见的有:
- 无重复字符的最长子串(第3题)
- 最长回文子串(第5题)
- 最小覆盖子串(第76题)
- 找到字符串中所有字母异位词(第438题)
2.1 无重复字符的最长子串
这个问题要求我们找到一个字符串中不包含重复字符的最长子串。最优解法是使用滑动窗口技术:
python复制def lengthOfLongestSubstring(s: str) -> int:
char_index = {} # 存储字符最后出现的位置
left = 0
max_len = 0
for right, char in enumerate(s):
if char in char_index and char_index[char] >= left:
left = char_index[char] + 1
char_index[char] = right
max_len = max(max_len, right - left + 1)
return max_len
这个算法的时间复杂度是O(n),空间复杂度是O(min(m,n)),其中m是字符集大小。
2.2 最长回文子串
寻找最长回文子串可以使用中心扩展法:
python复制def longestPalindrome(s: str) -> str:
def expandAroundCenter(left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return s[left+1:right]
result = ""
for i in range(len(s)):
# 奇数长度
palindrome = expandAroundCenter(i, i)
if len(palindrome) > len(result):
result = palindrome
# 偶数长度
palindrome = expandAroundCenter(i, i+1)
if len(palindrome) > len(result):
result = palindrome
return result
这个解法的时间复杂度是O(n^2),空间复杂度是O(1)。
3. 滑动窗口技术详解
滑动窗口是解决子串问题的利器,它的核心思想是维护一个窗口,通过调整窗口的左右边界来寻找符合条件的子串。
3.1 滑动窗口的基本框架
python复制def slidingWindow(s: str):
window = {} # 记录窗口内字符的计数
left, right = 0, 0
while right < len(s):
# 增大窗口
char = s[right]
window[char] = window.get(char, 0) + 1
right += 1
# 判断是否需要收缩窗口
while window needs shrink:
# 缩小窗口
char = s[left]
window[char] -= 1
if window[char] == 0:
del window[char]
left += 1
3.2 滑动窗口的变种
根据问题的不同,滑动窗口可以有多种变体:
- 固定大小窗口:如字符串中长度为k的子串问题
- 可变大小窗口:如无重复字符的最长子串
- 多指针窗口:某些复杂问题可能需要多个指针协同工作
4. 动态规划在子串问题中的应用
某些子串问题,特别是涉及回文或最优解的问题,适合用动态规划解决。
4.1 动态规划解最长回文子串
python复制def longestPalindrome(s: str) -> str:
n = len(s)
dp = [[False]*n for _ in range(n)]
res = ""
for i in range(n-1, -1, -1):
for j in range(i, n):
dp[i][j] = s[i] == s[j] and (j-i < 3 or dp[i+1][j-1])
if dp[i][j] and j-i+1 > len(res):
res = s[i:j+1]
return res
这个解法的时间复杂度是O(n^2),空间复杂度也是O(n^2)。
5. 常见错误与优化技巧
5.1 新手常犯的错误
- 混淆子串和子序列的概念
- 滑动窗口边界条件处理不当
- 忽略空字符串或单字符的特殊情况
- 时间复杂度分析错误
5.2 性能优化技巧
- 使用哈希表记录字符最后出现位置
- 对于固定字符集问题,可以用数组代替哈希表
- 提前终止不必要的计算
- 合理选择数据结构(如双端队列)
6. 实战演练:最小覆盖子串
让我们以LeetCode第76题为例,演示如何解决复杂子串问题:
python复制def minWindow(s: str, t: str) -> str:
from collections import defaultdict
target = defaultdict(int)
for char in t:
target[char] += 1
required = len(target)
formed = 0
window_counts = defaultdict(int)
result = (float('inf'), None, None)
left = 0
for right, char in enumerate(s):
if char in target:
window_counts[char] += 1
if window_counts[char] == target[char]:
formed += 1
while left <= right and formed == required:
if right - left + 1 < result[0]:
result = (right-left+1, left, right)
char = s[left]
if char in target:
window_counts[char] -= 1
if window_counts[char] < target[char]:
formed -= 1
left += 1
return "" if result[0] == float('inf') else s[result[1]:result[2]+1]
这个解法的时间复杂度是O(|S| + |T|),其中|S|和|T|分别是字符串s和t的长度。
7. 子串问题的扩展思考
掌握了基本子串问题后,可以尝试解决更复杂的变种:
- 包含所有字符的最短子串(允许字符顺序不同)
- 最多包含k个不同字符的最长子串
- 具有相同字符频率的子串数量
- 乘积小于k的子串数量
对于这类问题,通常需要在标准滑动窗口的基础上进行适当调整,比如引入额外的计数器或状态变量。
