You are given a string s
of length n
where s[i]
is either:
'D'
means decreasing, or'I'
means increasing.A permutation perm
of n + 1
integers of all the integers in the range [0, n]
is called a valid permutation if for all valid i
:
s[i] == 'D'
, then perm[i] > perm[i + 1]
, ands[i] == 'I'
, then perm[i] < perm[i + 1]
.Return the number of valid permutations perm
. Since the answer may be large, return it modulo 109 + 7
.
Example 1:
Input: s = "DID" Output: 5 Explanation: The 5 valid permutations of (0, 1, 2, 3) are: (1, 0, 3, 2) (2, 0, 3, 1) (2, 1, 3, 0) (3, 0, 2, 1) (3, 1, 2, 0)
Example 2:
Input: s = "D" Output: 1
Constraints:
n == s.length
1 <= n <= 200
s[i]
is either 'I'
or 'D'
.This problem asks to find the number of valid permutations of n+1
integers (from 0 to n) based on a given string s
of length n
. Each character in s
represents either an increasing ('I') or decreasing ('D') relationship between consecutive elements in the permutation.
The most efficient solution employs dynamic programming with prefix sums to reduce time complexity. Let's break down the approach:
1. Dynamic Programming State:
We define dp[i][j]
as the number of valid permutations of length i+1
that end with the integer j
. dp[0][0] = 1
because a permutation of length 1 (only containing 0) is always valid.
2. Recurrence Relation:
If s[i-1] == 'I'
(increasing): To form a valid permutation of length i+1
ending in j
, we must have the previous element be smaller than j
. Thus, we sum up all dp[i-1][k]
where 0 <= k < j
. This can be expressed as:
dp[i][j] = Σ dp[i-1][k] for k = 0 to j-1
If s[i-1] == 'D'
(decreasing): Similarly, the previous element must be greater than j
. The recurrence relation is:
dp[i][j] = Σ dp[i-1][k] for k = j to i-1
3. Prefix Sums Optimization:
Calculating the sums in the recurrence relations directly would lead to O(n^3) time complexity. We can optimize this using prefix sums. We maintain a prefix sum array prefixSum[i]
which represents the sum of all dp[i-1][k]
up to index k
. Then, the sums in the recurrence relations can be computed in O(1) time using prefixSum[j-1]
(for 'I') or prefixSum[i-1] - prefixSum[j]
(for 'D').
4. Space Optimization:
Notice that we only need the dp
array from the previous iteration (i-1
) to compute the current iteration (i
). This allows us to use a rolling array to reduce the space complexity to O(n).
5. Final Answer:
The final answer is the sum of all dp[n][j]
for j = 0 to n
, representing the total number of valid permutations. This sum is also calculated efficiently using the prefix sum array.
Time Complexity: O(n^2) due to the nested loops iterating through the string and integers. The prefix sum calculation is O(n) within each iteration.
Space Complexity: O(n) because of the rolling array dp
(and the temporary prefix sum array).
Code Example (Python with Space Optimization):
class Solution:
def numPermsDISequence(self, s: str) -> int:
mod = 10**9 + 7
n = len(s)
dp = [1] + [0] * n # Initialize dp array
for i, c in enumerate(s, 1):
new_dp = [0] * (n + 1) # create a new array for the next iteration
prefix_sum = 0
if c == "D":
for j in range(i, -1, -1):
prefix_sum = (prefix_sum + dp[j]) % mod
new_dp[j] = prefix_sum
else:
for j in range(i + 1):
new_dp[j] = prefix_sum
prefix_sum = (prefix_sum + dp[j]) % mod
dp = new_dp # update the dp array for the next iteration
return sum(dp) % mod
This Python code demonstrates the optimized dynamic programming approach with prefix sums and rolling array for space efficiency. Similar optimizations can be applied to other languages like Java, C++, Go, and TypeScript (as shown in the previous response). The key is to understand the recurrence relation, prefix sum technique, and rolling array strategy to achieve the optimal O(n^2) time and O(n) space complexity.