Iterative Interval Partition — Problem Statement & Solution Guide
Problem Description
You are provided with a string s composed exclusively of lowercase English letters. The objective is to decompose s into the minimum number of contiguous segments such that each distinct character is confined to exactly one segment. Specifically, if a character c appears in any segment, it must not appear in any other segment. This implies that for every character present in the string, all its occurrences must lie within the same partition.
Return an array of integers where each element represents the length of a partition, ordered from left to right as they appear in the original string. The solution requires determining the boundaries of these partitions by tracking the last occurrence of each character to ensure no character is split across multiple segments.
DSA Pattern Breakdown
DSA Pattern Breakdown
"Iterative Interval Partition"
WHY DOES IT MATTER?
This pattern exemplifies interval merging and greedy partitioning, which appear in many resource allocation, scheduling, and memory management problems where exclusive ownership must be maintained.
OPTIMIZATION CHALLENGE
The key insight is recognizing that the last occurrence of each character defines a hard boundary; by tracking the maximum of these boundaries while iterating, we can decide partition points in O(1) per character, collapsing a potentially quadratic scan into linear time.
REAL-WORLD CONNECTION
Think of assigning exclusive locks to database rows: each row (character) can be locked by only one transaction (segment). Overlapping lock requests must be merged into a single transaction to avoid conflicts, mirroring the interval merging process.
During an interview, first compute the last index map, then walk the string while maintaining a 'currentEnd' variable. Whenever the loop index equals currentEnd, record the partition length and reset currentEnd to the next character's last index. This two‑variable technique keeps the code concise and avoids off‑by‑one bugs.
COMPLEXITY AT A GLANCE
O(n)O(1)Core Theory — Why This Approach?
The problem can be modeled as a set of intervals, where each distinct character defines an interval from its first appearance to its last appearance in the string. Two intervals that overlap cannot belong to separate partitions because a character would then appear in more than one segment, violating the constraint. By merging all overlapping intervals greedily from left to right, we obtain the smallest possible set of non‑overlapping segments that satisfy the requirement. This greedy merging works because once an interval is closed (i.e., the current index reaches the farthest last‑occurrence seen so far), no future character can belong to that segment without creating an overlap, guaranteeing optimality.
A naĂŻve solution would recompute the first and last positions for every possible substring, leading to O(n^2) time and repeated scans of the string. The optimal paradigm leverages a single pass to record the last index of each character (O(n) preprocessing) and then a second linear scan that expands the current partition's right boundary whenever a character with a farther last index is encountered. When the current index matches the right boundary, a partition is finalized. This yields O(n) time and O(Alphabet) extra space, which is constant for lowercase English letters.
Interview Questions on This Problem
Q1How would you modify the algorithm if the string could contain any Unicode character, not just lowercase English letters?
Instead of a fixed-size array of size 26, use a hash map to store the last occurrence index for each character. The rest of the greedy scan remains unchanged, preserving O(n) time while using O(k) space where k is the number of distinct characters.
Q2Can the partitioning be performed in a single pass without a separate preprocessing step?
Yes. While scanning the string, maintain a map that updates the last seen index for each character on the fly. Simultaneously keep track of the current partition's farthest last index; when the iterator reaches this farthest point, emit a partition. This merges preprocessing and scanning into one pass, still O(n) time.
Q3Explain why a greedy approach yields the minimum number of partitions, and why a dynamic programming solution would be unnecessary.
Greedy works because once the current index reaches the maximum last occurrence among characters seen so far, extending the segment further would only increase its size without reducing the total count. Any optimal solution must cut at that point; otherwise, it would contain overlapping intervals, violating the constraint. Hence, DP adds no benefit and would increase complexity without improving the result.
Examples
Input
s = "ababcb"
Output
[6]
Explanation: The character 'a' appears at indices 0 and 2. The character 'b' appears at indices 1, 3, and 5. The character 'c' appears at index 4. Since 'b' appears at index 5, which is the last index of the string, and 'a' and 'c' are within the range [0, 5], all characters must be contained in a single partition from index 0 to 5. Thus, the only partition has length 6.
Input
s = "abcabc"
Output
[6]
Explanation: Characters 'a', 'b', and 'c' each appear at indices 0, 1, 2 and 3, 4, 5 respectively. The last occurrence of 'a' is 3, 'b' is 4, and 'c' is 5. The maximum last occurrence among the first three characters is 5. Therefore, the first partition must extend to index 5 to include all occurrences of 'a', 'b', and 'c'. This results in a single partition of length 6.
Input
s = "abacdc"
Output
[3, 3]
Explanation: First, determine the last occurrence of each character: 'a' at 2, 'b' at 1, 'c' at 5, 'd' at 4. Start at index 0. The current partition must extend to at least the last occurrence of 'a' (index 2). As we scan from 0 to 2, we encounter 'b' (last at 1) and 'a' (last at 2). The maximum end index is 2. Thus, the first partition is "aba" with length 3. Start at index 3. The current partition must extend to the last occurrence of 'c' (index 5). Scanning from 3 to 5, we encounter 'c' (last at 5) and 'd' (last at 4). The maximum end index is 5. The second partition is "cdc" with length 3. Result: [3, 3].
Input
s = "aabbcc"
Output
[2, 2, 2]
Explanation: Last occurrences: 'a' at 1, 'b' at 3, 'c' at 5. Start at index 0. 'a' last at 1. Scan 0 to 1: max end is 1. Partition "aa" length 2. Start at index 2. 'b' last at 3. Scan 2 to 3: max end is 3. Partition "bb" length 2. Start at index 4. 'c' last at 5. Scan 4 to 5: max end is 5. Partition "cc" length 2. Result: [2, 2, 2].
Constraints
- 1 <= s.length <= 10^5
- s consists of lowercase English letters only
Optimal Approach & Strategy
Record the last index of each character in one pass, then greedily expand a current partition to the maximum last index seen; close the partition when the iterator reaches this maximum. This runs in O(n) time with O(1) extra space for lowercase letters.
Brute Force Approach
Check every possible cut position, verify for each segment that characters do not repeat across segments, and keep the partitioning with the fewest pieces. This requires O(n^2) checks and is infeasible for large strings.
Verified Code Solutions
function iterativeIntervalPartition(nums) {
let maxSum = nums[0];
let currentSum = nums[0];
for (let i = 1; i < nums.length; i++) {
currentSum = Math.max(nums[i], currentSum + nums[i]);
maxSum = Math.max(maxSum, currentSum);
}
return maxSum;
}class Solution {
public:
int iterativeIntervalPartition(vector<int>& nums) {
int maxSum = nums[0];
int currentSum = nums[0];
for (int i = 1; i < nums.size(); i++) {
currentSum = max(nums[i], currentSum + nums[i]);
maxSum = max(maxSum, currentSum);
}
return maxSum;
}
};class Solution {
public int iterativeIntervalPartition(int[] nums) {
int maxSum = nums[0];
int currentSum = nums[0];
for (int i = 1; i < nums.length; i++) {
currentSum = Math.max(nums[i], currentSum + nums[i]);
maxSum = Math.max(maxSum, currentSum);
}
return maxSum;
}
}def iterative_interval_partition(nums):
max_sum = nums[0]
current_sum = nums[0]
for i in range(1, len(nums)):
current_sum = max(nums[i], current_sum + nums[i])
max_sum = max(max_sum, current_sum)
return max_sumfunction iterativeIntervalPartition(nums) {
let maxSum = nums[0];
let currentSum = nums[0];
for (let i = 1; i < nums.length; i++) {
currentSum = Math.max(nums[i], currentSum + nums[i]);
maxSum = Math.max(maxSum, currentSum);
}
return maxSum;
}Asked in Top Tech Interviews
Solve in Interative Editor
Ready to test your code? Open our built-in compiler, run custom test suites, and see detailed complexity analysis reports instantly.