Partition Labels
Problem
Given a string s, partition it into as many parts as possible so that each letter appears in at most one part, and return a list of integers representing the size of these parts.
- 1 ≤ s.length ≤ 500
- s consists of lowercase English letters only.
Example
s = "ababcbacadefegdehijhklij"[9,7,8]The algorithm first records the last index of each character: for example, 'a' last appears at index 8, 'b' at 5, 'c' at 7, etc. Then it iterates through the string, tracking the furthest last occurrence of any character seen so far (the 'end'). When the current index reaches this 'end', it means all characters in the current partition appear only within this range. The partition size is recorded (end - start + 1), and the process repeats from the next index. For the input, the partitions are [0..8], [9..15], and [16..23], with sizes 9, 7, and 8 respectively.
Approach
Straightforward Solution
A brute-force approach would try all possible partitions and check if characters repeat across partitions, which is exponential and infeasible.
Core Observation
Each character's last occurrence in the string defines a boundary that any partition containing that character must at least extend to. The partition boundary is the maximum last occurrence of all characters seen so far in the current partition.
Path to Optimal
The key recognition signals are 'partition string into parts', 'each letter appears in at most one part', and 'maximize number of parts'. These indicate a greedy approach because the problem reduces to finding the earliest point where all characters in the current partition have their last occurrence within the partition. By precomputing last occurrences, the algorithm can greedily extend the current partition's end to the furthest last occurrence of any character seen so far. When the current index reaches this end, a partition is finalized.
Optimal Approach
First, map each character to its last index in the string. Then iterate through the string, updating the current partition's end to the maximum last occurrence of characters encountered. When the current index equals the end, append the partition size to the result and reset the size counter. This greedy approach guarantees the maximum number of partitions with the required property.
Time
O(n)
The algorithm makes two passes over the string: one to record last occurrences and one to partition, each O(n). All operations inside loops are O(1).
Space
O(1)
The last occurrence map stores at most 26 entries for lowercase English letters, which is constant space. The output list scales with the number of partitions but is required by the problem.
Pattern Spotlight
Greedy Algorithms (Interval Partitioning)
When partitioning a sequence to satisfy a global constraint on element occurrences, track the furthest required boundary for all elements seen so far and finalize partitions at the earliest point where the current index meets this boundary.
Solution
| 1 | class Solution:
|
| 2 | def partitionLabels(self, s: str) -> list[int]:
|
| 3 | last_index = {}
|
| 4 | for i, c in enumerate(s):
|
| 5 | last_index[c] = i
|
| 6 |
|
| 7 | res = []
|
| 8 | size, end = 0, 0
|
| 9 | for i, c in enumerate(s):
|
| 10 | size += 1
|
| 11 | end = max(end, last_index[c])
|
| 12 |
|
| 13 | if i == end:
|
| 14 | res.append(size)
|
| 15 | size = 0
|
| 16 |
|
| 17 | return res |
Step-by-Step Solution
Record Last Occurrence of Each Character
| 3 | last_index = {}
|
| 4 | for i, c in enumerate(s):
|
| 5 | last_index[c] = i
|
Objective
To map each character in the string to its last index of appearance.
Key Insight
Knowing the last occurrence of each character is essential because it defines the minimum boundary any partition containing that character must reach. This precomputation enables the greedy extension of partition boundaries in a single pass.
Interview Quick-Check
Core Logic
Mapping characters to their last indices allows constant-time lookup of partition boundaries during iteration.
Complexity
This step is O(n) time and O(1) space since the character set is fixed (lowercase English letters).
Iterate to Build Partitions by Tracking Furthest Last Occurrence
| 7 | res = []
|
| 8 | size, end = 0, 0
|
| 9 | for i, c in enumerate(s):
|
| 10 | size += 1
|
| 11 | end = max(end, last_index[c])
|
| 13 | if i == end:
|
| 14 | res.append(size)
|
| 15 | size = 0
|
Objective
To traverse the string and dynamically update the current partition's end boundary based on characters seen.
Key Insight
By updating the partition end to the maximum last occurrence of any character encountered, the algorithm ensures that the partition includes all instances of those characters. When the current index reaches this end, it means the partition is complete and can be recorded.
Interview Quick-Check
Core Logic
The partition boundary is the maximum last occurrence of all characters seen so far; finalizing a partition when the current index equals this boundary ensures no character crosses partitions.
State & Boundaries
Resetting the partition size after appending ensures correct size calculation for subsequent partitions.
Common Pitfalls & Bugs
Failing to update the partition end to the maximum last occurrence can cause premature partitioning and violate the problem's constraints.
Return the List of Partition Sizes
| 17 | return res |
Objective
To output the computed list of partition sizes after processing the entire string.
Key Insight
Returning the accumulated partition sizes completes the solution, providing the required output format.
Interview Quick-Check
Core Logic
The result list contains sizes of partitions that satisfy the problem's constraints.
Line Analysis
This solution has 3 Critical lines interviewers watch for.
end = max(end, last_index[c])
Update the partition end to the maximum last occurrence of characters seen.
Extending the partition boundary to the furthest last occurrence guarantees that all instances of characters in the partition are included, preventing overlap with future partitions.
last_index[c] = i
Update the last occurrence of the current character.
Assigning the current index as the last occurrence ensures the dictionary always holds the furthest position for each character.
if i == end:
Check if the current index matches the partition end boundary.
When the current index reaches the partition end, it signifies that all characters in the partition are contained within this range, allowing the partition to be finalized.
last_index = {}
Initialize a dictionary to store the last index of each character.
This dictionary is the foundation for determining partition boundaries by recording where each character last appears in the string.
size = 0
Reset the partition size for the next partition.
Resetting size prepares for counting characters in the subsequent partition, ensuring sizes are accurate and independent.
size, end = 0, 0
Initialize variables to track current partition size and end boundary.
The size counts characters in the current partition, and end marks the furthest last occurrence of any character seen so far, defining the partition boundary.
res.append(size)
Append the current partition size to the result list.
Recording the partition size at this point captures the size of a valid partition that satisfies the problem constraints.
size += 1
Increment the current partition size.
Counting characters ensures accurate partition size measurement for when the partition is finalized.
for i, c in enumerate(s):
Iterate over the string with index and character.
Enumerating the string provides both the character and its position, enabling the update of last occurrence indices.
for i, c in enumerate(s):
Iterate over the string with index and character for partitioning.
This loop processes each character to update partition boundaries and sizes dynamically.
return res
Return the list of partition sizes.
Returning the accumulated partition sizes completes the solution, providing the required output.
res = []
Initialize the result list to store partition sizes.
This list accumulates the sizes of partitions as they are finalized during iteration.
Test Your Understanding
Why does finalizing a partition when the current index reaches the maximum last occurrence of all characters seen so far guarantee that no character appears in multiple partitions?
Because the partition boundary is extended to include the last occurrence of every character encountered, ensuring all instances of those characters lie within the partition. Thus, no character can appear beyond this boundary, preventing overlap across partitions.
Related Problems
Greedy pattern
Don't just read it. Drill it.
Reconstruct Partition Labels from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.
Unlock the Partition Labels drill