划分字母区间:每个字母的最后位置就是边界
片段里出现过的字母,外面不能再出现。右边界至少要推到这些字母最晚的那一次。
先扫一遍,记下每个字母最后出现的下标 last[c]。再从左扫:当前片段的 bound = max(bound, last[s[i]])。i 走到 bound,说明这段里出现过的字母,最后一次都落在这段里,可以切。记下长度 i−start+1,下一段从 i+1 开始。时间 O(n),空间 26 个格子。
这是 LeetCode 763. Partition Labels。把字符串 s 分成尽可能多的片段,使得每个字母只出现在其中一个片段里,返回每个片段的长度。片段必须保持原顺序、中间不能重排,也不能丢掉字符。
主例 s = "ababcbacadefegdehijhklij"。第一段必须吃到最后一个 a,也就是下标 8,这一段是 ababcbaca,长度 9;剩下 defegde,最后一个 e 在 15,长度 7;最后 hijhklij 长度 8。答案 [9, 7, 8]。若在下标 5 的 b 后面就切,后面的 a、c 还会再出现,违反「一字母一段」。
枚举切点再检查每个字母的出现范围,组合爆炸。缺的不是「能不能切」,而是每个位置上「这段至少还要盖到多远」。本文先建最后出现表,再让 i 去追这条不断被推远的 bound。
先问每个字母最后一次在哪
某个字母一旦进了当前片段,它在 s 里的最后一次出现也必须进这段——否则这段外面还会再见到它。所以「包含字母 c 的片段,右端至少是 last[c]」。
第一遍从左到右扫,last[c] 不断被更大的下标覆盖,结束时就是最后一次。只需 26 个整数。主例里 a 最后在 8,b 在 5,c 在 7,d 在 14,e 在 15,后面 h、i、j、k、l 分别在 19、22、23、20、21。演示里的表就是这张原材料。
只有这张表还不能切。a 把边界推到 8,但 0 到 8 之间还夹着 b、c,它们的最后位置不能超出 8,这段才封得住。下一步要用这张表在扫描时把边界推到「当前已见字母的最远 last」。
i 追上 bound,这一段才能封口
第二遍维护 start 和 bound,都从 0 开始。走到 s[i],把 bound 更新成 max(bound, last[s[i]])。bound 的含义:当前还没切开的这一段,右端至少要到这里。
当 i == bound,从 start 到 i 出现过的每个字母,它们的 last 都不超过 i——否则 bound 还会被推得更远。这时切开是安全的,也是尽可能早的:再往左切,就会把某个字母劈成两段。记下 i−start+1,start 改成 i+1。
用手走主例。i=0 读 a,bound=8。中间的 b、c 最后位置是 5 和 7,都推不动 8。i 走到 8,i==bound,切下长度 9,start=9。i=9 读 d,bound=14;i=10 读 e,bound=15;f、g 的 last 是 11、13,仍不超过 15。i 走到 15,再切长度 7,start=16。最后一段 h 把 bound 推到 19,i、j 继续推到 23,i 走到 23 切下 8。演示三帧就是这三刀,答案 [9, 7, 8]。
贪心在切,不在跳bound 只增不减。每一步都采用「当前已见字母要求的最远右端」,所以追上 bound 时既合法又最短。不会为了多切一刀而把同一字母拆开。
Go:last + 贪心切分
func partitionLabels(s string) []int {last := [26]int{}for i := range s { last[s[i]-'a'] = i }start, bound := 0, 0var res []intfor i := range s {if last[s[i]-'a'] > bound { bound = last[s[i]-'a'] }if i == bound {res = append(res, i-start+1)start = i + 1}}return res}
1第一遍覆盖写出每个字母最后下标。主例 last['a']=8,last['e']=15。
2bound 只在某个字母的最后位置更远时才被推大。
3i == bound 是唯一的切点。主例的 8、15、23 都落在这句。
4长度用 i−start+1,下一段从 i+1 起。不必把 bound 清零,下一轮会被新字母的 last 覆盖。
总结
先记每个字母最后一次,再让 i 去追 bound。主例在 8、15、23 切开,得到 [9,7,8]。
- 同一字母不能跨段,所以右端必须盖住这段里所有字母的 last。
- i 追上 bound 才切,切得尽可能早,段数也就尽可能多。
- 两遍线性扫描,额外空间只有 26 个整数。