想系统提升编程能力、查看更完整的学习路线,欢迎访问 AI Compass:github.com/tingaicompa… 仓库持续更新刷题题解、Python 基础和 AI 实战内容,适合想高效进阶的你。
📖 第68课:划分字母区间
模块:贪心算法 | 难度:Medium ⭐⭐ LeetCode 链接:leetcode.cn/problems/pa… 前置知识:无 预计学习时间:25分钟
🎯 题目描述
给定一个字符串s,将其划分为尽可能多的片段,使得每个字母最多出现在一个片段中。返回一个列表,表示每个片段的长度。
示例:
输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
片段1: "ababcbaca" (a,b,c只在这个片段中)
片段2: "defegde" (d,e,f,g只在这个片段中)
片段3: "hijhklij" (h,i,j,k,l只在这个片段中)
约束条件:
1 <= s.length <= 500s仅由小写英文字母组成- 划分要尽可能多,即每个片段尽可能短
🧪 边界用例(面试必考)
| 用例类型 | 输入 | 期望输出 | 考察点 |
|---|---|---|---|
| 最小输入 | s="a" | [1] | 单字符情况 |
| 无重复 | s="abcd" | [1,1,1,1] | 每个字符独立成片段 |
| 全相同 | s="aaaa" | [4] | 整个串是一个片段 |
| 交错出现 | s="ababcbaca" | [9] | a的最后出现位置决定边界 |
| 多片段 | s="eccbbbbdec" | [10] → [eccbbbbdec] | c出现在首尾,整体一片段 |
💡 思路引导
生活化比喻
想象你在整理一堆文件,每份文件上标有字母标签(a-z)。你的任务是把文件分成若干堆,要求:每个字母的所有文件必须在同一堆,并且堆数尽可能多(即每堆尽可能小)。
🐌 笨办法:从左往右扫描,遇到一个新字母就开始收集,直到把这个字母的所有文件都放进当前堆。但问题是,收集过程中可能遇到其他字母,它们的"最后一份文件"在更远的地方,你得继续收集...结果越收越多,很难确定何时停止。
🚀 聪明办法:先用一张索引表记录每个字母"最后一份文件"的位置,然后从左往右扫描。当前堆的"终点"就是"当前堆内所有字母的最后位置的最大值"。扫到终点时,这一堆就完成了,开启新一堆。这样每堆都是最短的,堆数最多!
关键洞察
预先记录每个字符最后出现的位置,然后一次扫描,贪心地扩展当前片段的右边界,直到扫描位置追上边界,就切分出一个片段。
🧠 解题思维链
这一节模拟你在面试中"从零开始思考"的过程。
Step 1:理解题目 → 锁定输入输出
- 输入:
s = "ababcbacadefegdehijhklij",26个小写字母组成的字符串 - 输出:
[9, 7, 8],每个片段的长度列表 - 限制:每个字母只能出现在一个片段中,片段数量要尽可能多(每个片段尽可能短)
Step 2:先想笨办法(暴力法)
从左往右扫描,遇到字符c时,找到c在整个串中最后出现的位置last_c,那么当前片段至少要延伸到last_c。但在扫描到last_c之前,可能遇到其他字符d,它的最后位置last_d可能更远,又得继续扩展...
这需要嵌套循环:
i = 0
while i < len(s):
end = i
for j in range(i, end+1):
last_pos = s.rfind(s[j]) # 每次都要查找最后位置
end = max(end, last_pos)
# [i, end]是一个片段
i = end + 1
- 时间复杂度:O(n²) — 外层循环n次,内层每次可能扫描O(n)
- 瓶颈在哪:重复调用
rfind查找字符最后位置,每次O(n)
Step 3:瓶颈分析 → 优化方向
核心问题:为什么每次都要重新查找字符的最后位置?字符集只有26个,可以预处理!
优化思路:
- 预处理阶段:用哈希表记录每个字符最后出现的位置,O(n)时间
- 扫描阶段:从左往右扫描,维护当前片段的右边界
end,遇到字符c就更新end = max(end, last[c]),当扫描位置i追上end时,片段完成
Step 4:选择武器
- 选用:哈希表预处理 + 贪心扫描
- 理由:
- 哈希表:O(1)查询字符最后位置,避免重复搜索
- 贪心:每次尽早切分片段(一旦
i==end立即切分),保证片段数最多
🔑 模式识别提示:当题目出现"字符/元素的最后(或最早)出现位置"+"区间划分",优先考虑哈希表预处理 + 贪心扫描模式
🔑 解法一:暴力扫描(直觉法)
思路
每次遇到新字符,就搜索它在整个串中的最后位置,不断扩展片段右边界,直到当前位置追上边界。
图解过程
示例:s = "ababcbacadefegde"
从i=0开始:
i=0, s[0]='a', 找a的最后位置=8 → end=8
i=1, s[1]='b', 找b的最后位置=5 → end=max(8,5)=8
i=2, s[2]='a', 最后位置还是8 → end=8
...
i=8, s[8]='a', i==end → 片段1完成:[0~8] 长度9
从i=9开始:
i=9, s[9]='d', 找d的最后位置=15 → end=15
i=10, s[10]='e', 找e的最后位置=14 → end=15
...
i=15, s[15]='e', i==end → 片段2完成:[9~15] 长度7
结果:[9,7]
Python代码
from typing import List
def partitionLabels_brute(s: str) -> List[int]:
"""
解法一:暴力扫描
思路:每次都用rfind查找字符最后位置,扩展片段边界
"""
result = []
i = 0
n = len(s)
while i < n:
end = i # 当前片段的右边界
# 扩展边界,直到包含所有当前片段内字符的最后位置
j = i
while j <= end:
last_pos = s.rfind(s[j]) # 查找s[j]的最后位置
end = max(end, last_pos)
j += 1
# 切分片段
result.append(end - i + 1)
i = end + 1
return result
# ✅ 测试
print(partitionLabels_brute("ababcbacadefegdehijhklij")) # 期望输出:[9,7,8]
print(partitionLabels_brute("eccbbbbdec")) # 期望输出:[10]
print(partitionLabels_brute("abcd")) # 期望输出:[1,1,1,1]
复杂度分析
- 时间复杂度:O(n²) — 外层while循环O(n),内层while中每次rfind是O(n),总体O(n²)
- 具体地说:如果
n=500,可能需要约25万次操作
- 具体地说:如果
- 空间复杂度:O(1) — 只用几个变量(不计结果数组)
优缺点
- ✅ 思路直观,无需预处理
- ✅ 空间占用少
- ❌ 时间复杂度高,重复查找造成浪费
- ❌ 不适合长字符串
🏆 解法二:哈希表预处理 + 贪心扫描(最优解)
优化思路
解法一的瓶颈在于重复调用rfind。我们可以用一次遍历预先记录每个字符的最后位置,然后一次扫描完成切分。
💡 关键想法:用O(n)时间预处理出字符位置字典,然后O(n)扫描,总体O(n),比暴力法快n倍!
图解过程
示例:s = "ababcbacadefegdehijhklij"
第1步:预处理,记录每个字符最后位置
遍历字符串,建立字典:
last = {
'a': 8, 'b': 5, 'c': 7,
'd': 14, 'e': 15, 'f': 11, 'g': 13,
'h': 19, 'i': 22, 'j': 23,
'k': 20, 'l': 21
}
第2步:贪心扫描,动态扩展片段右边界
start=0, end=0
i=0: s[0]='a', end=max(0, last['a']=8)=8
i=1: s[1]='b', end=max(8, last['b']=5)=8
i=2: s[2]='a', end=max(8, 8)=8
i=3: s[3]='b', end=max(8, 5)=8
i=4: s[4]='c', end=max(8, last['c']=7)=8
i=5: s[5]='b', end=max(8, 5)=8
i=6: s[6]='a', end=max(8, 8)=8
i=7: s[7]='c', end=max(8, 7)=8
i=8: s[8]='a', end=max(8, 8)=8
→ i==end → 切分片段[0~8],长度9
start=9, end=9
i=9: s[9]='d', end=max(9, last['d']=14)=14
i=10: s[10]='e', end=max(14, last['e']=15)=15
i=11: s[11]='f', end=max(15, last['f']=11)=15
i=12: s[12]='e', end=max(15, 15)=15
i=13: s[13]='g', end=max(15, last['g']=13)=15
i=14: s[14]='d', end=max(15, 14)=15
i=15: s[15]='e', end=max(15, 15)=15
→ i==end → 切分片段[9~15],长度7
start=16, end=16
i=16: s[16]='h', end=max(16, last['h']=19)=19
i=17: s[17]='i', end=max(19, last['i']=22)=22
i=18: s[18]='j', end=max(22, last['j']=23)=23
i=19: s[19]='h', end=max(23, 19)=23
i=20: s[20]='k', end=max(23, last['k']=20)=23
i=21: s[21]='l', end=max(23, last['l']=21)=23
i=22: s[22]='i', end=max(23, 22)=23
i=23: s[23]='j', end=max(23, 23)=23
→ i==end → 切分片段[16~23],长度8
结果:[9, 7, 8] ✅
可视化片段:
a b a b c b a c a | d e f e g d e | h i j h k l i j
←---- 片段1 ----→ ←-- 片段2 --→ ←--- 片段3 ---→
字符集{a,b,c} 字符集{d,e,f,g} 字符集{h,i,j,k,l}
为什么这样是最优的?
为什么片段1必须到8才能切分?
因为'a'最后出现在位置8,如果提前切分(如位置5),
那么'a'会同时出现在两个片段中,违反题目要求。
为什么一到8就立即切分?
因为位置8之前的所有字符(a,b,c)的最后出现位置都≤8,
它们不会在8之后出现,所以可以立即切分,让片段尽可能短。
这就是贪心策略:一旦能切就切,保证片段数最多。
Python代码
def partitionLabels(s: str) -> List[int]:
"""
解法二:哈希表预处理 + 贪心扫描(最优解)
思路:先记录每个字符最后位置,再一次扫描动态扩展片段边界
"""
# 第1步:预处理 - 记录每个字符最后出现的位置
last = {}
for i, char in enumerate(s):
last[char] = i # 不断更新,最终存的是最后位置
# 第2步:贪心扫描 - 动态扩展片段右边界
result = []
start = 0 # 当前片段起点
end = 0 # 当前片段右边界
for i, char in enumerate(s):
# 贪心:更新右边界为"当前片段内所有字符的最后位置的最大值"
end = max(end, last[char])
# 当扫描位置追上边界时,片段完成
if i == end:
result.append(end - start + 1) # 记录片段长度
start = end + 1 # 开启新片段
return result
# ✅ 测试
print(partitionLabels("ababcbacadefegdehijhklij")) # 期望输出:[9,7,8]
print(partitionLabels("eccbbbbdec")) # 期望输出:[10]
print(partitionLabels("abcd")) # 期望输出:[1,1,1,1]
print(partitionLabels("a")) # 期望输出:[1]
print(partitionLabels("aaaa")) # 期望输出:[4]
复杂度分析
- 时间复杂度:O(n) — 两次线性遍历,第一次建字典O(n),第二次扫描O(n),总体O(n)
- 具体地说:如果
n=500,只需约1000次操作,比暴力法的25万次快250倍!
- 具体地说:如果
- 空间复杂度:O(1) — 字典最多存26个小写字母,常数空间(不计结果数组)
为什么这是最优解?
- 时间已达理论下限:必须至少遍历一次字符串来确定字符位置,O(n)已是最优
- 空间也是最优:只用26个键的字典,O(1)常数空间
- 贪心策略正确性:一旦
i==end就立即切分,保证每个片段最短,片段数最多 - 代码简洁高效:核心逻辑不到15行,易于理解和实现
⚡ 解法三:双指针变体(思路补充)
思路
本质与解法二相同,用不同的代码组织形式:记录片段的左右边界,逐步扩展。
Python代码
def partitionLabels_v2(s: str) -> List[int]:
"""
解法三:双指针变体
思路:显式维护片段的[left, right]边界
"""
# 预处理:记录每个字符最后位置
last = {char: i for i, char in enumerate(s)}
result = []
left, right = 0, 0
for i, char in enumerate(s):
right = max(right, last[char]) # 扩展右边界
if i == right: # 到达边界,切分片段
result.append(right - left + 1)
left = right + 1 # 下一个片段起点
return result
# ✅ 测试
print(partitionLabels_v2("ababcbacadefegdehijhklij")) # 期望输出:[9,7,8]
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(1)
与解法二本质相同,只是代码风格不同。
🐍 Pythonic 写法
利用字典推导式和enumerate的简洁版:
def partitionLabels_pythonic(s: str) -> List[int]:
"""Pythonic写法:字典推导式 + 紧凑逻辑"""
# 一行构建last字典
last = {char: i for i, char in enumerate(s)}
result = []
start = end = 0
for i, char in enumerate(s):
end = max(end, last[char])
if i == end:
result.append(end - start + 1)
start = end + 1
return result
特点:
- 用字典推导式
{char: i for i, char in enumerate(s)}一行构建last - 逻辑紧凑,保持可读性
- 适合向面试官展示Python语言功底
⚠️ 面试建议:先写解法二的标准版本展示清晰思路,再提Pythonic写法展示语言功底。面试官更看重你的算法设计能力,而非炫技。
📊 解法对比
| 维度 | 解法一:暴力扫描 | 🏆 解法二:哈希表+贪心(最优) | 解法三:双指针变体 |
|---|---|---|---|
| 时间复杂度 | O(n²) | O(n) ← 时间最优 | O(n) |
| 空间复杂度 | O(1) | O(1) ← 空间最优 | O(1) |
| 代码难度 | 简单(但低效) | 简单(15行) | 简单 |
| 面试推荐 | ⭐ | ⭐⭐⭐ ← 首选 | ⭐⭐ |
| 适用场景 | 仅作对比理解 | 面试首选,工程实用 | 思路补充 |
为什么解法二是最优解:
- 时间O(n)已达理论最优:必须至少遍历一次字符串,不可能更快
- 空间O(1)也是最优:字符集固定(26个字母),字典大小为常数
- 预处理+贪心策略高效:一次预处理避免重复查找,贪心保证片段数最多
- 代码清晰简洁:逻辑分两步(预处理→扫描),易于理解和实现
面试建议:
- 先用30秒简述暴力思路(O(n²)),表明你理解问题:"每次用rfind查找最后位置"
- 立即优化到🏆解法二(O(n)):"我们可以预处理出字符位置字典,避免重复查找"
- 重点画图讲解贪心过程:用
[ababcbacadefegde]演示如何动态扩展边界 - 强调为什么这是最优:时间O(n)、空间O(1),预处理是关键优化点
- 对比类似题目:与"合并区间"类似,都是贪心地维护区间边界
🎤 面试现场
模拟面试中的完整对话流程,帮你练习"边想边说"。
面试官:请你解决一下这道划分字母区间的问题。
你:(审题30秒)好的,这道题要求把字符串分成若干片段,每个字母只能出现在一个片段中,并且片段数要尽可能多。
我的第一个想法是,每次遇到字符就用rfind找它的最后位置,不断扩展片段边界,时间复杂度O(n²)。但这样会重复查找,效率不高。
更优的方法是哈希表预处理 + 贪心扫描:先用O(n)时间遍历一次,记录每个字符最后出现的位置,存到字典里。然后再遍历一次,维护当前片段的右边界end,遇到字符c就更新end = max(end, last[c]),当扫描位置i追上end时,说明片段内所有字符都不会再往后出现了,可以切分。这样时间O(n),空间O(1)。
面试官:很好,请写一下代码并解释。
你:(边写边说)
def partitionLabels(s):
# 第1步:预处理 - 记录每个字符最后位置
last = {}
for i, char in enumerate(s):
last[char] = i
# 第2步:贪心扫描 - 动态扩展片段边界
result = []
start = end = 0
for i, char in enumerate(s):
# 更新右边界为当前片段内所有字符的最后位置的最大值
end = max(end, last[char])
# 一旦扫描位置追上边界,切分片段
if i == end:
result.append(end - start + 1)
start = end + 1
return result
核心思想:
- 预处理阶段:建立字符→最后位置的映射,避免重复查找
- 贪心阶段:动态扩展片段右边界,一旦
i==end就立即切分,保证片段最短
面试官:测试一下?
你:用"ababcbaca"走一遍:
- 预处理:last = {'a':8, 'b':5, 'c':7}
- 扫描:
- i=0, char='a', end=max(0,8)=8
- i=1~7, end保持或更新,但都≤8
- i=8, end=8, i==end → 切分片段[0~8],长度9 ✅
再测边界情况"abcd":
- last = {'a':0, 'b':1, 'c':2, 'd':3}
- 每个字符最后位置就是自己,所以每次i==end都立即切分
- 结果:[1,1,1,1] ✅
面试官:为什么last字典的空间复杂度是O(1)而不是O(n)?
你:因为字符集是固定的26个小写字母,不管字符串多长,字典最多存26个键值对,这是常数。所以空间复杂度是O(1)。如果题目改成Unicode字符,那就得按O(n)算了。
高频追问
| 追问 | 应答策略 |
|---|---|
| "还有更优解吗?" | 时间O(n)已经是最优(必须遍历字符串),空间O(1)也是最优(字符集固定)。无法再优化。 |
| "如果字符集非常大(如Unicode)?" | 哈希表空间会变成O(k),k是字符集大小,但算法思路不变。可以用defaultdict简化。 |
| "能否只遍历一次?" | 不行,必须先知道每个字符的最后位置才能正确切分。虽然是两次遍历,但都是O(n),总体还是O(n)。 |
| "这道题和'合并区间'有什么关系?" | 相似点:都是贪心维护区间边界。区别:'合并区间'是给定区间求并集,'划分字母区间'是根据字符位置动态生成区间边界。 |
| "如果要求返回片段本身而不是长度?" | 只需修改返回语句:result.append(s[start:end+1]),其他逻辑不变。 |
🎓 知识点总结
Python技巧卡片 🐍
# 技巧1:字典推导式构建映射
last = {char: i for i, char in enumerate(s)}
# 技巧2:enumerate同时获取索引和值
for i, char in enumerate(s):
...
# 技巧3:max动态更新最大值
end = max(end, last[char])
# 技巧4:多变量同时赋值初始化
start = end = 0
# 技巧5:字典的键存在性检查(本题未用,但相关)
if char in last: # O(1)查询
...
💡 底层原理(选读)
为什么哈希表查询是O(1)?
Python的dict底层是用哈希表(Hash Table)实现的:
- 键通过哈希函数转化为数组索引:
index = hash(key) % array_size - 直接通过索引访问值,不需要遍历,所以是O(1)
- 哈希冲突用链表或开放寻址解决,平均情况仍是O(1)
本题中:
- 键:'a'~'z' 共26个字符
- 值:最后出现的位置(整数)
- 查询
last[char]是O(1),比s.rfind(char)的O(n)快n倍
贪心算法的适用场景:
- 局部最优 = 全局最优:每次选择当前最优的决策,不会影响后续的最优性
- 无后效性:当前决策不依赖未来状态
本题中:
- 局部最优:一旦
i==end就立即切分,让当前片段最短 - 为什么全局最优:因为所有字符的最后位置已知,提前切分不会破坏后续片段的合法性
- 无后效性:已切分的片段不影响后续片段的划分
与"合并区间"(LeetCode 56)的对比:
| 维度 | 合并区间 | 划分字母区间 |
|---|---|---|
| 输入 | 给定区间列表 | 字符串(隐式生成区间) |
| 操作 | 合并重叠区间 | 切分非重叠区间 |
| 预处理 | 按起点排序 | 记录字符最后位置 |
| 核心 | 比较当前区间与前一区间 | 动态扩展片段边界 |
| 共同点 | 贪心维护区间边界 | 贪心维护区间边界 |
算法模式卡片 📐
- 模式名称:字符位置哈希 + 贪心区间切分
- 适用条件:
- 需要频繁查询元素的位置(首次、最后、所有)
- 区间划分/合并问题
- 要求尽可能多/少的区间数
- 识别关键词:
- "每个字母/元素最多出现在一个..."
- "尽可能多的片段"
- "最后出现的位置"
- 模板代码:
def partition_template(s: str) -> List[int]:
# 第1步:预处理 - 记录关键信息(首次/最后位置)
last = {}
for i, char in enumerate(s):
last[char] = i # 或first[char] = first.get(char, i)
# 第2步:贪心扫描 - 动态维护区间边界
result = []
start = end = 0
for i, char in enumerate(s):
end = max(end, last[char]) # 扩展右边界
if i == end: # 到达边界,切分
result.append(end - start + 1)
start = end + 1
return result
易错点 ⚠️
-
预处理阶段错误:
- ❌ 错误:用列表记录所有出现位置
positions['a'] = [0, 2, 8] - ✅ 正确:只记录最后位置
last['a'] = 8 - 原因:只需最后位置就能确定片段边界,记录所有位置浪费空间
- ❌ 错误:用列表记录所有出现位置
-
边界更新错误:
- ❌ 错误:
end = last[char](直接赋值) - ✅ 正确:
end = max(end, last[char])(取最大值) - 原因:片段内可能有多个字符,边界是所有字符最后位置的最大值,不能被后面的小值覆盖
- ❌ 错误:
-
切分条件错误:
- ❌ 错误:
if i >= end(用>=) - ✅ 正确:
if i == end(用==) - 原因:
i不可能>end,因为每次循环都会更新end到至少i的位置
- ❌ 错误:
-
忘记更新start:
- ❌ 错误:切分后只append长度,不更新start
- ✅ 正确:
start = end + 1 - 原因:下一个片段从
end+1开始,不更新会导致长度计算错误
-
混淆"尽可能多"和"尽可能少":
- 题目要求片段数尽可能多 = 每个片段尽可能短
- 策略:一旦
i==end就立即切分,不等待 - 如果要求片段数尽可能少,那就要尽量延迟切分(不同问题)
🏗️ 工程实战(选读)
这个算法思想在真实项目中的应用,让你知道"学了有什么用"。
-
场景1:日志文件分片 在分布式系统中,日志按用户ID记录。为了并行处理,需要把日志分成多个片段,要求同一用户的日志必须在同一片段中,且片段数尽可能多(便于并行)。这道题的算法可以直接应用:预处理出每个用户ID最后出现的行号,然后贪心切分。
-
场景2:数据库分区 在数据库水平分区(Sharding)中,需要把数据按某个键(如user_id)分配到不同分区,要求同一键的所有记录在同一分区。预先统计键的分布范围,然后用贪心算法划分区间,最小化跨分区查询。
-
场景3:视频剪辑自动分段 在视频编辑软件中,根据字幕/台词把视频自动分段,要求同一个人的连续台词在同一段中。预处理出每个说话人最后出现的时间戳,然后贪心切分时间轴,生成多个视频片段。
🏋️ 举一反三
完成本课后,试试这些同类题目来巩固知识:
| 题目 | 难度 | 相关知识点 | 提示 |
|---|---|---|---|
| LeetCode 56. 合并区间 | Medium | 贪心,排序+区间合并 | 先按起点排序,然后贪心合并重叠区间,与本题互为对偶 |
| LeetCode 435. 无重叠区间 | Medium | 贪心,按终点排序 | 求最少删除多少区间使剩余无重叠,贪心选择终点最早的 |
| LeetCode 452. 用最少的箭引爆气球 | Medium | 贪心,区间交集 | 本质是"合并区间"变体,贪心选择射击位置 |
| LeetCode 986. 区间列表的交集 | Medium | 双指针,区间合并 | 给定两个区间列表,求交集,用双指针扫描 |
| LeetCode 228. 汇总区间 | Easy | 区间表示 | 给定有序数组,找出连续区间,用双指针/贪心 |
📝 课后小测
试试这道变体题,不要看答案,自己先想5分钟!
题目:给定字符串s,找出最长的子串,使得子串中每个字符最多出现一次。返回该子串的长度。
例如:s = "abcabcbb",最长子串是"abc",长度为3。
💡 提示(实在想不出来再点开)
这题是滑动窗口经典题(第14课:无重复字符的最长子串)。用哈希表记录窗口内字符的位置,当发现重复字符时,收缩左边界。与本题的区别是:本题要切分多个片段,那题要找一个最长片段。
✅ 参考答案
def lengthOfLongestSubstring(s: str) -> int:
"""
最长无重复子串:滑动窗口 + 哈希表
思路:维护窗口[left, right],用哈希表记录字符位置
"""
seen = {} # 记录字符最近出现的位置
left = 0
max_len = 0
for right, char in enumerate(s):
# 如果字符在窗口内重复,收缩左边界
if char in seen and seen[char] >= left:
left = seen[char] + 1 # 左边界移到重复字符的下一个位置
seen[char] = right # 更新字符位置
max_len = max(max_len, right - left + 1) # 更新最大长度
return max_len
# 测试
print(lengthOfLongestSubstring("abcabcbb")) # 期望输出:3 ("abc")
print(lengthOfLongestSubstring("bbbbb")) # 期望输出:1 ("b")
print(lengthOfLongestSubstring("pwwkew")) # 期望输出:3 ("wke")
核心思路:
- 用滑动窗口
[left, right]维护当前无重复子串 - 用哈希表
seen记录字符最近出现的位置 - 当发现重复字符时,收缩左边界到重复位置的下一个
- 持续更新最大长度
与本课题目的对比:
| 维度 | 本课(划分字母区间) | 无重复最长子串 |
|---|---|---|
| 目标 | 切分多个片段 | 找一个最长片段 |
| 预处理 | 记录字符最后位置 | 不需要预处理 |
| 核心算法 | 贪心扩展边界 | 滑动窗口动态调整 |
| 复杂度 | O(n) | O(n) |
如果这篇内容对你有帮助,推荐收藏 AI Compass:github.com/tingaicompa… 更多系统化题解、编程基础和 AI 学习资料都在这里,后续复习和拓展会更省时间。