942. DI String Match
Problem Description
You are given a string s
that describes the relationship between consecutive elements in a permutation. The permutation contains all integers from 0
to n
(where n
is the length of string s
), arranged in some order.
The string s
works like this:
- If
s[i] = 'I'
, it means the element at positioni
in the permutation is less than the element at positioni+1
(increasing) - If
s[i] = 'D'
, it means the element at positioni
in the permutation is greater than the element at positioni+1
(decreasing)
Your task is to construct any valid permutation that satisfies these conditions.
For example, if s = "IDID"
:
- The permutation has 5 elements (integers 0 through 4)
s[0] = 'I'
meansperm[0] < perm[1]
s[1] = 'D'
meansperm[1] > perm[2]
s[2] = 'I'
meansperm[2] < perm[3]
s[3] = 'D'
meansperm[3] > perm[4]
One valid permutation would be [0, 4, 1, 3, 2]
because:
0 < 4
(satisfies 'I')4 > 1
(satisfies 'D')1 < 3
(satisfies 'I')3 > 2
(satisfies 'D')
The solution uses a greedy approach with two pointers. It maintains low
starting at 0 and high
starting at n
. When encountering 'I', it uses the current smallest available number (low
), and when encountering 'D', it uses the current largest available number (high
). This ensures all conditions are satisfied while using each number exactly once.
Intuition
Think about what happens when we see an 'I' - we need the current number to be smaller than the next one. The safest way to guarantee this is to use the smallest available number right now. Why? Because any number we place next will definitely be larger than the smallest possible value.
Similarly, when we see a 'D', we need the current number to be larger than the next one. The safest choice is to use the largest available number, because whatever comes next will definitely be smaller.
This greedy strategy works because:
- When we place the smallest number for 'I', we're leaving room above it for future placements
- When we place the largest number for 'D', we're leaving room below it for future placements
Let's trace through an example with s = "IDID"
:
- Start with available numbers
[0, 1, 2, 3, 4]
- See 'I': Use smallest (0), now we have
[1, 2, 3, 4]
left - See 'D': Use largest (4), now we have
[1, 2, 3]
left - See 'I': Use smallest (1), now we have
[2, 3]
left - See 'D': Use largest (3), now we have
[2]
left - End of string: Use the last remaining number (2)
Result: [0, 4, 1, 3, 2]
The key insight is that by always choosing extremes (smallest for 'I', largest for 'D'), we create maximum "buffer space" between consecutive elements, ensuring the required relationships are always satisfied. This approach guarantees we'll never get stuck in a situation where we can't satisfy a future constraint.
Learn more about Greedy and Two Pointers patterns.
Solution Approach
The implementation uses a two-pointer greedy algorithm with the following steps:
-
Initialize two pointers:
low = 0
(tracks the smallest available number)high = len(s)
(tracks the largest available number, which equalsn
)
-
Create an empty result array
ans
to build our permutation. -
Iterate through each character in string
s
:- If character is
'I'
:- Append
low
to the result array - Increment
low
by 1 (the next smallest available number)
- Append
- If character is
'D'
:- Append
high
to the result array - Decrement
high
by 1 (the next largest available number)
- Append
- If character is
-
Handle the last element: After processing all characters in
s
, we've placedn
elements but needn+1
total. The last remaining number islow
(which equalshigh
at this point), so append it to complete the permutation.
Here's how the algorithm works step by step:
def diStringMatch(self, s: str) -> List[int]:
low, high = 0, len(s) # Initialize pointers
ans = []
for c in s:
if c == "I":
ans.append(low) # Use smallest available
low += 1 # Update smallest
else: # c == "D"
ans.append(high) # Use largest available
high -= 1 # Update largest
ans.append(low) # Add the last remaining number
return ans
Time Complexity: O(n)
where n
is the length of string s
. We traverse the string once.
Space Complexity: O(1)
if we don't count the output array. We only use two integer variables for tracking.
The algorithm guarantees correctness because:
- When we place
low
for 'I', all remaining numbers are greater thanlow
, ensuring the next number will be larger - When we place
high
for 'D', all remaining numbers are smaller thanhigh
, ensuring the next number will be smaller - We use each number from
0
ton
exactly once
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with s = "DI"
:
Step 1: Initialize
- String
s = "DI"
has length 2, so we need a permutation of [0, 1, 2] - Set
low = 0
(smallest available) - Set
high = 2
(largest available) - Create empty result array
ans = []
Step 2: Process first character 'D'
- Since it's 'D', we need current element > next element
- Use the largest available: append
high = 2
to ans - Update
high = 2 - 1 = 1
- State:
ans = [2]
, available numbers: [0, 1]
Step 3: Process second character 'I'
- Since it's 'I', we need current element < next element
- Use the smallest available: append
low = 0
to ans - Update
low = 0 + 1 = 1
- State:
ans = [2, 0]
, available numbers: [1]
Step 4: Add the last element
- We've processed all characters but need one more element
- Both
low
andhigh
equal 1 (the only remaining number) - Append
low = 1
to ans - Final result:
ans = [2, 0, 1]
Verification:
- Position 0 to 1:
2 > 0
โ (satisfies 'D') - Position 1 to 2:
0 < 1
โ (satisfies 'I') - All numbers from 0 to 2 used exactly once โ
The greedy strategy ensures we always have valid numbers available by using extremes - the largest for 'D' leaves smaller numbers for future use, and the smallest for 'I' leaves larger numbers for future use.
Solution Implementation
1class Solution:
2 def diStringMatch(self, s: str) -> List[int]:
3 """
4 Generate a permutation of integers from 0 to n based on DI string pattern.
5
6 For each 'I' (increase), use the current smallest available number.
7 For each 'D' (decrease), use the current largest available number.
8 This greedy approach ensures the pattern is satisfied.
9
10 Args:
11 s: A string containing only 'D' and 'I' characters
12
13 Returns:
14 A list of integers forming a valid permutation
15 """
16 # Initialize pointers for smallest and largest available numbers
17 low_pointer = 0
18 high_pointer = len(s)
19
20 # Result array to store the permutation
21 result = []
22
23 # Process each character in the string
24 for char in s:
25 if char == "I":
26 # For increase, use the smallest available number
27 result.append(low_pointer)
28 low_pointer += 1
29 else: # char == "D"
30 # For decrease, use the largest available number
31 result.append(high_pointer)
32 high_pointer -= 1
33
34 # Add the last remaining number (low_pointer and high_pointer converge to same value)
35 result.append(low_pointer)
36
37 return result
38
1class Solution {
2 /**
3 * Given a string s of 'I' (increase) and 'D' (decrease) characters,
4 * returns a permutation of integers [0, 1, ..., n] where n = s.length()
5 * such that for all i:
6 * - If s[i] == 'I', then result[i] < result[i+1]
7 * - If s[i] == 'D', then result[i] > result[i+1]
8 *
9 * @param s string containing only 'I' and 'D' characters
10 * @return permutation array satisfying the increase/decrease pattern
11 */
12 public int[] diStringMatch(String s) {
13 int stringLength = s.length();
14
15 // Initialize pointers for smallest and largest available values
16 int smallestAvailable = 0;
17 int largestAvailable = stringLength;
18
19 // Result array has one more element than the string length
20 int[] result = new int[stringLength + 1];
21
22 // Process each character in the string
23 for (int i = 0; i < stringLength; i++) {
24 if (s.charAt(i) == 'I') {
25 // For 'I', use the smallest available value and increment it
26 result[i] = smallestAvailable;
27 smallestAvailable++;
28 } else {
29 // For 'D', use the largest available value and decrement it
30 result[i] = largestAvailable;
31 largestAvailable--;
32 }
33 }
34
35 // Place the last remaining value (smallestAvailable == largestAvailable at this point)
36 result[stringLength] = smallestAvailable;
37
38 return result;
39 }
40}
41
1class Solution {
2public:
3 vector<int> diStringMatch(string s) {
4 // Get the length of the input string
5 int n = s.size();
6
7 // Initialize two pointers: low starts from 0, high starts from n
8 // We'll use these to assign values based on 'I' or 'D' characters
9 int low = 0;
10 int high = n;
11
12 // Result array will have n+1 elements (one more than string length)
13 vector<int> result(n + 1);
14
15 // Iterate through each character in the string
16 for (int i = 0; i < n; ++i) {
17 if (s[i] == 'I') {
18 // For 'I' (increase), assign the current low value and increment it
19 // This ensures the next value will be greater
20 result[i] = low;
21 low++;
22 } else {
23 // For 'D' (decrease), assign the current high value and decrement it
24 // This ensures the next value will be smaller
25 result[i] = high;
26 high--;
27 }
28 }
29
30 // Assign the last element (at this point, low == high)
31 result[n] = low;
32
33 return result;
34 }
35};
36
1/**
2 * Given a string s of 'I' and 'D' characters, returns a permutation of integers [0, 1, ..., n]
3 * where n = s.length, such that:
4 * - If s[i] == 'I', then result[i] < result[i + 1]
5 * - If s[i] == 'D', then result[i] > result[i + 1]
6 *
7 * @param s - String containing only 'I' and 'D' characters
8 * @returns Array of integers representing the valid permutation
9 */
10function diStringMatch(s: string): number[] {
11 // Initialize result array to store the permutation
12 const result: number[] = [];
13
14 // Initialize pointers for smallest and largest available numbers
15 // Range is from 0 to s.length (inclusive)
16 let lowestValue: number = 0;
17 let highestValue: number = s.length;
18
19 // Process each character in the string
20 for (const character of s) {
21 if (character === 'I') {
22 // For 'I' (increase), use the current lowest value and increment it
23 result.push(lowestValue);
24 lowestValue++;
25 } else {
26 // For 'D' (decrease), use the current highest value and decrement it
27 result.push(highestValue);
28 highestValue--;
29 }
30 }
31
32 // Add the last remaining number (lowestValue and highestValue converge to the same value)
33 result.push(lowestValue);
34
35 return result;
36}
37
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the string s
.
The algorithm iterates through each character in the string s
exactly once using a single for loop. Each iteration performs constant-time operations: comparing a character, appending to a list, and incrementing or decrementing a counter. After the loop, there's one additional append operation. Therefore, the total time complexity is O(n)
.
Space Complexity: O(n)
, where n
is the length of the string s
.
The algorithm creates an output list ans
that stores n + 1
integers (one for each character in s
plus one final append). The additional variables low
and high
use constant space O(1)
. Since the output array dominates the space usage with O(n + 1) = O(n)
elements, the overall space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Add the Last Element
The most common mistake is forgetting that the permutation needs n+1
elements when the string has length n
. After processing all characters in s
, you've only placed n
elements.
Incorrect Implementation:
def diStringMatch(self, s: str) -> List[int]:
low, high = 0, len(s)
ans = []
for c in s:
if c == "I":
ans.append(low)
low += 1
else:
ans.append(high)
high -= 1
# Missing: ans.append(low)
return ans # Returns only n elements instead of n+1
Solution: Always remember to append the final remaining number after the loop. At this point, low
and high
have converged to the same value, so you can append either one.
2. Incorrect Initial Values for Pointers
Another pitfall is initializing the pointers incorrectly, such as starting both at 0 or using wrong boundary values.
Incorrect Implementation:
def diStringMatch(self, s: str) -> List[int]:
low, high = 0, len(s) - 1 # Wrong! high should be len(s)
# or
low, high = 1, len(s) # Wrong! low should start at 0
Solution: Always initialize low = 0
(smallest value in range) and high = len(s)
(largest value in range, which is n
).
3. Off-by-One Error in Understanding the Problem
Some might misunderstand that the permutation should contain numbers from 1
to n
or from 0
to n-1
.
Incorrect Understanding:
# Wrong: Using 1 to n instead of 0 to n
low, high = 1, len(s) + 1
Solution: Carefully read the problem - the permutation contains all integers from 0
to n
inclusive, where n = len(s)
.
4. Using the Wrong Pointer for Each Character
Mixing up when to use low
vs high
will produce an incorrect permutation.
Incorrect Implementation:
for c in s: if c == "I": ans.append(high) # Wrong! Should use low for 'I' high -= 1 else: ans.append(low) # Wrong! Should use high for 'D' low += 1
Solution: Remember the mnemonic:
- 'I' for Increase โ use the smallest (low) to guarantee the next can be larger
- 'D' for Decrease โ use the largest (high) to guarantee the next can be smaller
Which of the following is a min heap?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donโt Miss This!