1. 项目概述
今天要分享的是LeetCode经典题目中的两道中等难度题——"无重复字符的最长子串"和"赎金信"。这两道题分别考察了滑动窗口和计数数组这两个算法技巧,在实际面试中出现频率极高。作为刷过300+题的过来人,我发现很多同学容易在这类问题上陷入暴力解法的误区,今天就用最直白的语言拆解这两道题的优化思路。
2. 无重复字符的最长子串
2.1 问题描述
给定一个字符串s,找出其中不含有重复字符的最长子串的长度。例如:
- 输入:"abcabcbb" → 输出:3("abc")
- 输入:"bbbbb" → 输出:1("b")
2.2 暴力解法分析
最直观的做法是双重循环检查所有子串:
python复制def lengthOfLongestSubstring(s):
max_len = 0
for i in range(len(s)):
for j in range(i, len(s)):
if len(set(s[i:j+1])) == j-i+1:
max_len = max(max_len, j-i+1)
return max_len
时间复杂度O(n³),在LeetCode上会直接超时。
2.3 滑动窗口优化
更聪明的做法是维护一个滑动窗口:
python复制def lengthOfLongestSubstring(s):
char_index = {} # 记录字符最后出现位置
left = 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)
注意:判断条件中的
char_index[char] >= left是为了处理"abba"这类情况
2.4 常见错误
- 忘记更新字符位置字典
- 左边界移动时直接取
left = right(应该取left = char_index[char] + 1) - 没有处理空字符串的特殊情况
3. 赎金信问题
3.1 问题描述
给定两个字符串ransomNote和magazine,判断ransomNote是否能由magazine中的字符组成(每个字符只能用一次)。例如:
- 输入:ransomNote="aa", magazine="aab" → 输出:True
- 输入:ransomNote="aa", magazine="ab" → 输出:False
3.2 计数数组解法
python复制def canConstruct(ransomNote, magazine):
count = [0] * 26
for c in magazine:
count[ord(c) - ord('a')] += 1
for c in ransomNote:
index = ord(c) - ord('a')
count[index] -= 1
if count[index] < 0:
return False
return True
3.3 优化技巧
- 提前终止:如果ransomNote比magazine长,直接返回False
- 空间优化:对于Unicode字符可以使用字典代替数组
- Pythonic写法:可以使用collections.Counter
3.4 易错点
- 忘记处理大小写敏感(题目通常说明是小写字母)
- 没有考虑magazine为空的情况
- 计数时混淆了ransomNote和magazine的遍历顺序
4. 滑动窗口与计数数组的关联
虽然这两道题看似不同,但核心都是对字符出现次数的统计:
- 滑动窗口:动态维护一个合法区间内的字符计数
- 计数数组:静态统计所有字符的出现次数
在实际面试中,经常会将这两种技巧结合使用。比如"找到包含所有指定字符的最短子串"这类题目,就需要同时运用这两种方法。
5. 同类题目推荐
为了巩固这两个技巧,建议练习:
-
滑动窗口系列:
-
- 长度最小的子数组
-
- 水果成篮
-
- 最大连续1的个数 III
-
-
计数数组系列:
-
- 有效的字母异位词
-
- 找到字符串中所有字母异位词
-
- 字符串的排列
-
6. 个人刷题心得
- 对于滑动窗口问题,一定要先在纸上画出窗口移动的示意图
- 计数数组初始化时建议用
[0] * 26而不是[0 for _ in range(26)](效率更高) - 遇到字符串问题先明确字符集范围(ASCII/Unicode)
- Python中使用
ord()比直接减字符慢,在性能敏感场景可以预计算偏移量
最后分享一个调试技巧:对于滑动窗口问题,可以在循环内打印左右边界和当前窗口内容,这样能快速定位逻辑错误。
