Skip to main content

Partition Labels

Given a string s containing only lowercase characters, return a list of integers representing the size of each substring you can create such that each character in s only appears in one substring.

Note: Each character must appear in exactly one partition, and we want to partition the string into as many parts as possible.

Example(s)​

Example 1:

Input: s = "abacdddecn"
Output: [3, 6, 1]
Explanation:
- Partition 1: "aba" (size 3)
Characters 'a' and 'b' only appear in this partition
- Partition 2: "cdddec" (size 6)
Characters 'c' and 'd' only appear in this partition
- Partition 3: "n" (size 1)
Character 'n' only appears in this partition

Example 2:

Input: s = "aba"
Output: [3]
Explanation:
- Partition 1: "aba" (size 3)
Character 'a' appears at positions 0 and 2, so they must be in the same partition

Example 3:

Input: s = "ababcbacadefegdehijhklij"
Output: [9, 7, 8]
Explanation:
- Partition 1: "ababcbaca" (size 9)
- Partition 2: "defegde" (size 7)
- Partition 3: "hijhklij" (size 8)

Solution​

The solution uses a greedy approach with two passes:

  1. First pass: Find the last occurrence of each character
  2. Second pass: Traverse the string and track the furthest position we need to reach
  3. Partition detection: When we reach the furthest position, we've completed a partition
  4. Record size: Add the size of the current partition to the result

JavaScript Solution

/**
* Partition string into substrings with unique characters
* @param {string} s - Input string
* @return {number[]} - Sizes of each partition
*/
function partitionLabels(s) {
// Step 1: Find the last occurrence of each character
const lastOccurrence = new Map();
for (let i = 0; i < s.length; i++) {
  lastOccurrence.set(s[i], i);
}

const result = [];
let start = 0; // Start of current partition
let end = 0;   // End of current partition (furthest position we need to reach)

// Step 2: Traverse the string and create partitions
for (let i = 0; i < s.length; i++) {
  // Update end to the furthest last occurrence of any character seen so far
  end = Math.max(end, lastOccurrence.get(s[i]));
  
  // If we've reached the end of the current partition
  if (i === end) {
    // Calculate size of current partition
    result.push(end - start + 1);
    // Start a new partition
    start = i + 1;
  }
}

return result;
}

// Test cases
console.log('Example 1:', partitionLabels("abacdddecn")); 
// [3, 6, 1]

console.log('Example 2:', partitionLabels("aba")); 
// [3]

console.log('Example 3:', partitionLabels("ababcbacadefegdehijhklij")); 
// [9, 7, 8]

console.log('Test 4:', partitionLabels("eccbbbbdec")); 
// [10]

console.log('Test 5:', partitionLabels("abc")); 
// [1, 1, 1]
Output:
Click "Run Code" to execute the code and see the results.

Alternative Solution (More Explicit)​

Here's a more explicit version that might be easier to understand:

/**
* Alternative: More explicit version
*/
function partitionLabelsExplicit(s) {
// Find last occurrence of each character
const lastIndex = {};
for (let i = 0; i < s.length; i++) {
lastIndex[s[i]] = i;
}

const partitions = [];
let partitionStart = 0;
let partitionEnd = 0;

for (let i = 0; i < s.length; i++) {
const char = s[i];
const lastPos = lastIndex[char];

// Extend the current partition to include this character's last occurrence
partitionEnd = Math.max(partitionEnd, lastPos);

// If we've processed all characters up to the partition end
if (i === partitionEnd) {
// Record the size of this partition
partitions.push(partitionEnd - partitionStart + 1);
// Start a new partition
partitionStart = i + 1;
}
}

return partitions;
}

Complexity​

  • Time Complexity: O(n) - Where n is the length of the string. We make two passes through the string: one to find last occurrences, and one to create partitions.
  • Space Complexity: O(1) - We use a map to store last occurrences, but since there are at most 26 lowercase letters, the space is O(1). The result array is not counted in space complexity.

Approach​

The solution uses a greedy two-pass algorithm:

  1. First pass - Find last occurrences:

    • Create a map storing the last index where each character appears
    • This tells us how far we need to extend each partition
  2. Second pass - Create partitions:

    • Traverse the string from left to right
    • For each character, update end to the maximum of:
      • Current end value
      • Last occurrence of current character
    • When i === end, we've completed a partition:
      • Record the size: end - start + 1
      • Start a new partition at i + 1

Key Insights​

  • Last occurrence matters: If a character appears at position i, we must include it in the partition until its last occurrence
  • Greedy approach: We extend the current partition as far as necessary
  • Partition boundary: When we reach the furthest position needed, we can safely end the partition
  • Two-pass algorithm: First pass to gather information, second pass to use it
  • O(n) time: Linear time complexity is optimal

Step-by-Step Example​

Let's trace through Example 1: s = "abacdddecn"

Step 1: Find last occurrences
'a': last at index 2
'b': last at index 1
'c': last at index 7
'd': last at index 6
'e': last at index 8
'n': last at index 9

Step 2: Create partitions
i=0: char='a', last=2, end=max(0,2)=2
i=1: char='b', last=1, end=max(2,1)=2
i=2: char='a', last=2, end=max(2,2)=2
i === end β†’ partition complete!
size = 2 - 0 + 1 = 3
result = [3]
start = 3

i=3: char='c', last=7, end=max(0,7)=7
i=4: char='d', last=6, end=max(7,6)=7
i=5: char='d', last=6, end=max(7,6)=7
i=6: char='d', last=6, end=max(7,6)=7
i=7: char='e', last=8, end=max(7,8)=8
i=8: char='c', last=7, end=max(8,7)=8
i === end β†’ partition complete!
size = 8 - 3 + 1 = 6
result = [3, 6]
start = 9

i=9: char='n', last=9, end=max(0,9)=9
i === end β†’ partition complete!
size = 9 - 9 + 1 = 1
result = [3, 6, 1]

Final: [3, 6, 1]

Visual Representation​

String:  a b a c d d d e c n
Index: 0 1 2 3 4 5 6 7 8 9

Last occurrence:
a: 2, b: 1, c: 7, d: 6, e: 8, n: 9

Partition 1: [0-2] "aba" (size 3)
- 'a' appears at 0, 2 β†’ must include up to 2
- 'b' appears at 1 β†’ must include up to 1
- Furthest: 2

Partition 2: [3-8] "cdddec" (size 6)
- 'c' appears at 3, 8 β†’ must include up to 8
- 'd' appears at 4, 5, 6 β†’ must include up to 6
- 'e' appears at 7 β†’ must include up to 8
- Furthest: 8

Partition 3: [9-9] "n" (size 1)
- 'n' appears at 9 β†’ must include up to 9

Edge Cases​

  • All unique characters: s = "abc" β†’ [1, 1, 1] (each character is its own partition)
  • All same character: s = "aaa" β†’ [3] (all must be in one partition)
  • Single character: s = "a" β†’ [1]
  • No repeats: s = "abcdef" β†’ [1, 1, 1, 1, 1, 1]
  • All repeats: s = "ababab" β†’ [6] (all must be together)

Important Notes​

  • Each character appears once: The constraint is that each character appears in exactly one partition
  • Maximize partitions: We want as many partitions as possible (greedy)
  • Last occurrence rule: If a character appears multiple times, all occurrences must be in the same partition
  • Lowercase only: Problem states only lowercase characters, so we have at most 26 unique characters
  • Merge Intervals: Similar concept of grouping related elements
  • Non-overlapping Intervals: Related interval problem
  • Partition Array: Different partitioning problem
  • Group Anagrams: Grouping related strings

Takeaways​

  • Two-pass algorithm is efficient: gather information first, then use it
  • Greedy approach works: extend partition as far as needed
  • Last occurrence tracking is the key insight
  • O(n) time is optimal for this problem
  • Hash map is perfect for tracking last occurrences