DI String Match
EasyPrompt
A permutation perm of n + 1 integers of all the integers in the range [0, n] can be represented as a string s of length n where:
s[i] == 'I'ifperm[i] < perm[i + 1], ands[i] == 'D'ifperm[i] > perm[i + 1].
Given a string s, reconstruct the permutation perm and return it. If there are multiple valid permutations perm, return any of them.
Example 1:
Input: s = "IDID"
Output: [0,4,1,3,2]Example 2:
Input: s = "III"
Output: [0,1,2,3]Example 3:
Input: s = "DDI"
Output: [3,2,0,1]
Constraints:
1 <= s.length <= 105s[i]is either'I'or'D'.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach builds the permutation step-by-step, from left to right. At each position, it tries to place an unused number that satisfies the current 'I' or 'D' constraint. If it hits a dead end, it backtracks and tries a different number.
Algorithm
- Initialize an empty permutation
permof sizen+1and a boolean arrayusedof sizen+1to track used numbers. - Define a recursive function
backtrack(k, perm, used). - Base Case: If
k == n + 1, a valid permutation has been found. Returntrue. - Recursive Step: Iterate
numfrom0ton.- If
used[num]isfalse:- If
k > 0, check the constraint withs[k-1]:- If
s[k-1] == 'I'andperm[k-1] >= num, this choice is invalid. Continue to the nextnum. - If
s[k-1] == 'D'andperm[k-1] <= num, this choice is invalid. Continue to the nextnum.
- If
- Place the number:
perm[k] = num,used[num] = true. - Make a recursive call:
if (backtrack(k + 1, perm, used)) return true;. - Backtrack:
used[num] = false.
- If
- If
- If the loop finishes without finding a solution, return
false. - Start the process by calling
backtrack(0, perm, used).
Walkthrough
We define a recursive function, say solve(index, current_perm, used_numbers). The function tries to fill current_perm[index]. The base case for the recursion is when index reaches n+1, which means we have successfully constructed a full permutation. In the recursive step, we iterate through all numbers from 0 to n. For each number num, if it has not been used yet, we check if placing it at current_perm[index] is valid by checking against current_perm[index-1] and s[index-1]. If it's valid, we place the number, mark it as used, and make a recursive call for solve(index + 1, ...). If the recursive call fails, we backtrack by undoing the choice and trying the next available number.
class Solution { public int[] diStringMatch(String s) { int n = s.length(); int[] perm = new int[n + 1]; boolean[] used = new boolean[n + 1]; backtrack(0, perm, used, s); return perm; } private boolean backtrack(int k, int[] perm, boolean[] used, String s) { int n = s.length(); if (k == n + 1) { return true; // Found a valid permutation } for (int num = 0; num <= n; num++) { if (!used[num]) { if (k > 0) { if (s.charAt(k - 1) == 'I' && perm[k - 1] >= num) { continue; } if (s.charAt(k - 1) == 'D' && perm[k - 1] <= num) { continue; } } perm[k] = num; used[num] = true; if (backtrack(k + 1, perm, used, s)) { return true; } used[num] = false; // Backtrack } } return false; }}Complexity
Time
O((n+1)!) in the worst case. Although pruning reduces the search space compared to brute-force, the complexity remains exponential.
Space
O(n) for the recursion stack depth and the `used` array.
Trade-offs
Pros
More efficient than brute-force as it prunes invalid branches of the search tree early.
It's a general strategy for solving constraint satisfaction problems.
Cons
Still too slow for the problem's constraints as the time complexity is fundamentally factorial/exponential.
Can lead to stack overflow for large
ndue to deep recursion.
This approach involves generating every possible permutation of the numbers from 0 to n. For each permutation, we check if it satisfies the conditions given by the input string s. The first permutation that satisfies the conditions is returned.
Algorithm
- Let
nbe the length of the input strings. - Generate all permutations of the integers
[0, 1, ..., n]. - For each permutation
p:- Check if
pis a valid DI string match. - To check, iterate from
i = 0ton-1:- If
s[i] == 'I'andp[i] > p[i+1],pis not a match. Break and check the next permutation. - If
s[i] == 'D'andp[i] < p[i+1],pis not a match. Break and check the next permutation.
- If
- If the loop completes without breaking,
pis a valid match. Returnp.
- Check if
Walkthrough
The algorithm first needs a way to generate all permutations of numbers [0, 1, ..., n]. This can be done using a standard algorithm like Heap's algorithm or by using recursion and backtracking. For each generated permutation perm, we construct a temporary string based on perm. For each i from 0 to n-1, if perm[i] < perm[i+1], we would expect an 'I'; otherwise, we'd expect a 'D'. We compare this expectation with the input string s. If they match for all positions, we have found a valid permutation, and we can return it. Since the problem guarantees that a solution exists, this process will eventually find one.
class Solution { public int[] diStringMatch(String s) { int n = s.length(); int[] perm = new int[n + 1]; for (int i = 0; i <= n; i++) { perm[i] = i; } // This do-while loop with nextPermutation generates all permutations do { if (isValid(perm, s)) { return perm; } } while (nextPermutation(perm)); return new int[0]; // Should not be reached } private boolean isValid(int[] perm, String s) { for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == 'I' && perm[i] >= perm[i + 1]) { return false; } if (s.charAt(i) == 'D' && perm[i] <= perm[i + 1]) { return false; } } return true; } // A standard implementation of the next permutation algorithm private boolean nextPermutation(int[] nums) { int i = nums.length - 2; while (i >= 0 && nums[i] >= nums[i + 1]) { i--; } if (i < 0) { return false; } int j = nums.length - 1; while (nums[j] <= nums[i]) { j--; } swap(nums, i, j); reverse(nums, i + 1); return true; } private void swap(int[] nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } private void reverse(int[] nums, int start) { int i = start, j = nums.length - 1; while (i < j) { swap(nums, i, j); i++; j--; } }}Complexity
Time
O(n * (n+1)!). There are `(n+1)!` permutations of `n+1` elements. For each permutation, we perform a check that takes `O(n)` time.
Space
O(n) for storing the permutation. The recursion stack for generating permutations can also go up to O(n).
Trade-offs
Pros
Conceptually simple to understand as it directly translates the problem definition.
Guaranteed to find a solution since one is guaranteed to exist.
Cons
Extremely inefficient due to its factorial time complexity.
Will cause a 'Time Limit Exceeded' error for even moderately small inputs (e.g., n > 10), making it impractical for the given constraints.
This is a highly efficient, linear-time approach. The key insight is that to satisfy an 'I' (increase) condition, we should pick the smallest available number to leave maximum room for future larger numbers. Conversely, for a 'D' (decrease) condition, we should pick the largest available number to leave room for smaller numbers.
Algorithm
- Let
nbe the length ofs. - Initialize
low = 0andhigh = n. - Initialize an result array
permof sizen+1. - Iterate
ifrom0ton-1:- If
s.charAt(i) == 'I':- Set
perm[i] = low. - Increment
low.
- Set
- Else (
s.charAt(i) == 'D'):- Set
perm[i] = high. - Decrement
high.
- Set
- If
- After the loop, one position in
permis left to be filled (perm[n]) andlowwill equalhigh. - Set
perm[n] = low(orhigh). - Return
perm.
Walkthrough
We maintain two pointers, low and high, initialized to 0 and n respectively. These pointers represent the smallest and largest numbers currently available from the range [0, n]. We iterate through the input string s from i = 0 to n-1, building the permutation perm from left to right. For each character s[i], if it's 'I', we greedily choose the smallest available number (low) for perm[i] and increment low. This ensures that any subsequent number chosen will be greater. If s[i] is 'D', we greedily choose the largest available number (high) for perm[i] and decrement high, ensuring any subsequent number will be smaller. After the loop, exactly one number remains, and low will be equal to high. This last number is placed at perm[n].
class Solution { public int[] diStringMatch(String s) { int n = s.length(); int[] perm = new int[n + 1]; int low = 0; int high = n; for (int i = 0; i < n; i++) { if (s.charAt(i) == 'I') { perm[i] = low++; } else { // s.charAt(i) == 'D' perm[i] = high--; } } // The last element will be the only number left. // At this point, low == high. perm[n] = low; return perm; }}Complexity
Time
O(n), where n is the length of the string `s`. We iterate through the string once.
Space
O(n) or O(1) auxiliary space. We need `O(n)` space for the output array `perm`. If the output array is not considered auxiliary space, the space complexity is `O(1)`.
Trade-offs
Pros
Optimal time complexity, solving the problem in a single pass.
Optimal space complexity, using constant extra space (if the output array is not counted).
Simple to implement and understand once the greedy logic is clear.
Cons
The greedy choice might not be immediately obvious without some thought and analysis.
Solutions
Solution
class Solution {public int[] diStringMatch(String s) { int n = s.length(); int low = 0, high = n; int[] ans = new int[n + 1]; for (int i = 0; i < n; i++) { if (s.charAt(i) == 'I') { ans[i] = low++; } else { ans[i] = high--; } } ans[n] = low; return ans; }}class Solution {public: vector<int> diStringMatch(string s) { int n = s.size(); int low = 0, high = n; vector<int> ans(n + 1); for (int i = 0; i < n; ++i) { if (s[i] == 'I') { ans[i] = low++; } else { ans[i] = high--; } } ans[n] = low; return ans; }};class Solution: def diStringMatch(self, s: str) -> List[int]: n = len(s) low, high = 0, n ans = [] for i in range(n): if s[i] == 'I': ans . append(low) low += 1 else: ans . append(high) high -= 1 ans . append(low) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.