763. 划分字母区间
LeetCode 7631 · 中等 · 贪心算法
题
把字符串 s 切成尽可能多的片段,要求每个字母只出现在一个片段里,返回各片段长度。
例:s = "ababcbacadefegdehijhklij" → [9, 7, 8]("ababcbaca"、"defegde"、"hijhklij")。
一句话
每个字母的「首次到末次」是一个必须整体落在同一段的区间;从左往右合并重叠区间,每合并出一块独立区间就是一段。
关键技巧
预处理末次出现位置。 last[c] = 字母 c 最后一次出现的下标。一旦段里出现了 c,这段至少要延伸到 last[c]。
扫描推右界。 维护当前段的右界 end = max(end, last[s[i]])。i == end 时,段内所有字母的末次出现都不超过 i,后面不会再出现它们——此刻切开是合法的。
为什么贪心不亏。 能切就立刻切:在 i == end 处切,前面这段是满足约束的最短前缀;后面剩下的串和原问题同构、互不影响。任何合法划分的第一刀都不可能早于这里(否则某字母跨段),所以最早切给出的段数最多。
解
class Solution:
def partitionLabels(self, s: str) -> List[int]:
last = {c: i for i, c in enumerate(s)} # 后写覆盖前写,得到末次位置
res, start, end = [], 0, 0
for i, c in enumerate(s):
end = max(end, last[c])
if i == end: # 段内字母都已收尾
res.append(end - start + 1)
start = i + 1
return res
时间 \(O(n)\),空间 \(O(\Sigma)\),\(\Sigma\) 为字符集大小(26)。
延伸
- 区间合并的本体:56. 合并区间。本题等价于把每个字母的
[首次, 末次]合并,只是按扫描顺序天然有序,不用排序。 - 「最远可达」同一个变量:55. 跳跃游戏 的
reach与这里的end是一回事——都在推一个区间右界。
自测
-
LeetCode 763,2026-09-29 读。 ↩