763. Partition Labels
Problem Statement: Cut a string into as many parts as possible so that no letter appears in more than one part. Return the sizes of the parts in order.
Input: s = "ababcbacadefegdehijhklij"
Output: [9, 7, 8]
Picture it
Every letter owns a stretch of the string, from where it first appears to where it last appears. For a part to be valid, it has to contain each of its letters' stretches entirely, otherwise that letter would also show up in a later part.
So walk forward with a part open, and keep a deadline: the furthest last-appearance of any letter seen so far inside this part.
"ababcbaca..."
a last appears at 8, so meeting an 'a' pushes the deadline to 8
b last appears at 5, which is already inside, so nothing changes
Every new letter can push the deadline further out. When you finally arrive at the deadline, every letter in this part has finished, so the part can close.
How to think about it
Two passes.
First, record the last position of every letter. One sweep, 26 entries at most, and this is what makes the deadline available immediately.
Second, walk the string holding the start of the current part and the deadline. At each position, stretch the deadline to include this letter's last appearance. When the position equals the deadline, record the part's size, and start the next part at the following position.
The greedy claim is that closing as soon as the deadline is met is right. Closing earlier would split some letter across two parts, which is not allowed. Closing later would merge two valid parts into one, giving fewer parts when the problem asks for as many as possible. So the first legal moment is always the correct one.
Note the resemblance to the interval problems: each letter is really an interval from first to last appearance, and a part is a group of overlapping ones merged together. The difference is that the intervals arrive in order of their starts for free, so no sorting is needed.
Walking through the example
Take s = "ababcbacadefegdehijhklij".
- Position 0 holds
a, whose last appearance is at8. The deadline becomes8. - Positions 1 to 7 hold
b,a,b,c,b,a,c. The lastbis at5and the lastcis at7, both already inside, so the deadline stays8. - Position 8 holds
a, and the position now equals the deadline. Close the part: it runs from0to8, a size of9. - Position 9 holds
d, last at14, so the new deadline is14. The letterse,fandgfollow, withelast at15, pushing the deadline to15. - Position 15 meets the deadline, closing a part of size
7. - The rest,
hijhklij, has its furthest last appearance at the end of the string, giving a part of size8.
The answer is [9, 7, 8].
The code
class Solution:
def partitionLabels(self, s: str) -> list[int]:
# where each letter appears for the last time
last = {ch: i for i, ch in enumerate(s)}
out = []
start = 0
deadline = 0
for i, ch in enumerate(s):
# this part cannot end before this letter does
deadline = max(deadline, last[ch])
if i == deadline:
out.append(i - start + 1)
start = i + 1
return out
Building the last dictionary by simple assignment works because later positions overwrite earlier ones, leaving each letter mapped to its final appearance.
Time and space complexity
Time:
Two passes over the string, each doing constant work per character.
Space:
The dictionary holds at most one entry per letter of the alphabet, which does not grow with the length of the string.
Remember this
Each letter spans from its first to its last appearance, and a part must contain those spans whole. Record every letter's last position first, then sweep holding a deadline that each new letter can push further out, closing the part the moment the position reaches it. Closing at the first legal moment is what maximises the number of parts. time, space.