Skip to content

763. Partition Labels (Medium)

Problem

Given a string s, partition it into as many parts as possible so that each letter appears in at most one part. Return the sizes of the parts.

Example

  • s = "ababcbacadefegdehijhklij"[9, 7, 8]
  • s = "eccbbbbdec"[10]

LeetCode 763 · Link · Medium

Try it yourself

idle

Starter code: this editor begins with intentional TODOs. Fill the function, run the embedded tests, then compare your solution with the worked approaches below.

Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: Brute force, try all partition points

For each candidate split, verify that every character on the left never appears on the right. Quadratic.

def partition_labels(s):
if not s:
return []
n = len(s)
# Find the smallest k such that s[:k] and s[k:] share no character
for k in range(1, n + 1):
if not (set(s[:k]) & set(s[k:])): # L1: O(n) per k
return [k] + partition_labels(s[k:]) # L2: recurse on the rest
return [n]

Building both sets is O(n)O(n) per candidate cut, and we try up to n cuts → O(n2)O(n²) total.

Complexity

  • Time: O(n2)O(n²).
  • Space: O(n)O(n).

Approach 2: Precompute last-seen index + greedy extension (canonical)

Precompute last[ch], the final index of each character. Walk the string; maintain a running end = the max last[ch] seen so far. When the walking index reaches end, the current window is the smallest valid partition ending at end.

def partition_labels(s):
last = {ch: i for i, ch in enumerate(s)} # L1: O(n), last occurrence of each char
result = [] # L2: O(1)
start = end = 0 # L3: O(1)
for i, ch in enumerate(s): # L4: single pass, n iterations
end = max(end, last[ch]) # L5: O(1), extend window if needed
if i == end: # L6: O(1), window is closed
result.append(i - start + 1) # L7: O(1) amortized
start = i + 1 # L8: O(1)
return result

Where the time goes, line by line

Variables: n = len(s).

LinePer-call costTimes executedContribution
L1 (build last map)O(1)O(1)nO(n)O(n)
L4-L8 (greedy scan)O(1)O(1)nO(n)O(n) ← dominates
L5 (extend end)O(1)O(1)nO(n)O(n)
L6-L8 (emit partition)O(1)O(1) amortizedat most nO(n)O(n)

Two O(n)O(n) passes: one to build last, one to scan and emit partitions.

Complexity

  • Time: O(n)O(n), driven by L4/L5/L6-L8 (two linear passes).
  • Space: O(26)O(26) = O(1)O(1) for ASCII alphabets.

Why greedy works

The moment the walking index equals end, every character in [start, end] is fully contained in the window (no later occurrence anywhere past end). So [start, end] is valid, and it’s the smallest such window, any earlier cut would miss a later occurrence of some character.

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 3: Union-Find / interval-merge formulation (conceptually equivalent)

Each character’s first and last occurrence form an interval. Merge overlapping intervals; the merged sizes are the answer. Same O(n)O(n) via the same last-index trick, included as a conceptual map to problem 56 (Merge Intervals).

def partition_labels(s):
first, last = {}, {}
for i, ch in enumerate(s):
first.setdefault(ch, i)
last[ch] = i
intervals = sorted((first[ch], last[ch]) for ch in first) # L1: O(k log k), k ≤ 26
merged = []
for a, b in intervals: # L2: linear sweep
if merged and a <= merged[-1][1]:
merged[-1] = (merged[-1][0], max(merged[-1][1], b))
else:
merged.append((a, b))
return [b - a + 1 for a, b in merged]

Same answer, derived as the canonical “sort + sweep” merge-intervals template applied to per-character ranges. With a fixed alphabet (e.g., 26 letters), the sort is constant.

Complexity

  • Time: O(n)O(n) given the bounded alphabet.
  • Space: O(1)O(1) extra (alphabet-sized).

Try this approach:

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Summary

ApproachTimeSpace
Try all splitsO(n2)O(n²)O(1)O(1)
Last-seen + greedy extensionO(n)O(n)O(1)O(1)

Pattern: “first index encountering a character starts its interval; max(last[ch]) grows the window.”

Test cases

func partitionLabels(s string) []int {
last := make(map[byte]int)
for i := 0; i < len(s); i++ { last[s[i]] = i }
var result []int
start, end := 0, 0
for i := 0; i < len(s); i++ {
if last[s[i]] > end { end = last[s[i]] }
if i == end { result = append(result, i-start+1); start = i + 1 }
}
return result
}
  • Greedy Algorithms, the local choice pattern protected by an invariant about the best reachable future.
  • Hash Map Counting, the lookup table pattern for complements, frequencies, and seen items.