题目
We are given S, a length n string of characters from the set {‘D’, ‘I’}. (These letters stand for “decreasing” and “increasing”.)
A valid permutation is a permutation P[0], P[1], …, P[n] of integers {0, 1, …, n}, such that for all i:
If S[i] == ‘D’, then P[i] > P[i+1], and;
If S[i] == ‘I’, then P[i] < P[i+1].
How many valid permutations are there? Since the answer may be large, return your answer modulo 10^9 + 7.
Input: “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)
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-permutations-for-di-sequence
简单来讲,题目要求遇到D则要求下一个数小于当前数,求这样的排列数目。
初始思路
生成过程可以使用森林画出来,如DID,数字从0到3。可画森林如下:
如此,可以发现dfs可以模仿生成过程并计数,时间复杂度为O(N * N!),这个时间复杂度怕是在很多用例下一定会超时。
但是可以发现我们有很多操作实际上都是重复计算,比如找3后面比他小的内容,可以说在每一个里面都会遇到这样的情况。
DP方法
子问题发现
- 这里有一个很巧妙的点在于我们可以在一个已经存在的序列上将新的数值X加入,原序列中大于等于X的内容全部加一,这样就生成了新的序列。所以子问题就是没有X前的状态,新问题就是加入了X是什么状态。
- 现在我们就可以从后往前看,加入以X作为最后一个数字,然后保证符合DI字符串要求。
示例过程
还是以DID为例,通过画图来发现这个过程。图的示例如下:
- 首先考虑初始情况(最小问题):如果DI字符串S为空,长度为0,说明只可以存在一个数字0,那么共有一种解法就是0
- 考虑DI字符串加入D,现在存在两个数字{0, 1}。要求现在的数小于前一个数。
我们从上图以0为第一个数的基础上看,- 如果要加0,则将原先大于等于0的值加1,也就是0加一,再追加当前值。
- 如果要加1,则会发现前面的值必须要比1大才行,所以1不会生成结果
- w如此我们就在初始状态下生成了一个DI字符串为一个D的情况。
- 接下来考虑加入I,DI字符串变为DI。继续从上方1->0的基础上看,现在数字多了一个2,数字集合为{0, 1, 2}。
- 考虑加入0:这里我们直接看末尾数字是否小于0(满足I的要求),则需要从尾数小于0的来看,发现没有;
- 考虑加入1:继续看尾数,从小于1的尾数发现存在1->0的这个排列,则对排列的数字大于等于1的全部加1,最后把1加到最后。
- 考虑加入2:看看尾数为0和1的,对大于等于2的数字加1,再把2加到最后
加入1 |
---|
加入2 |
---|
- 最后考虑加入D,DI字符串变为DID。数字集合为{0,1,2,3}
- 考虑末尾加入0,则从尾数大于等于0的情况下找,大于等于0的序列全部加1
- 加入1,2,3则同理,这里直接画图如下:
加入0 |
---|
加入1 |
---|
加入2 |
---|
由于结尾没有比3大的数字,所以3无法加入。最后结果为5个。
现在我们可以抽象出这个过程,i可以视为S的索引,为了方便索引使用,给S前面添加一个空,用来存储原始状态,即S[0] = ’ '。此外设置dp为二维数组,dp[i][j]存储以j为最后一个元素的前i个数字。
后面每一次i++,添加新的数字,则遍历i的长度,并根据当前S是I还是D,决定遍历的起止点。
通过抽象过程,DP转移式如下所示:
d
p
[
i
]
[
j
]
=
∑
k
=
j
i
−
1
d
p
[
i
−
1
]
[
k
]
,
S
[
i
]
=
=
′
D
′
dp[i][j]=\sum_{k=j}^{i-1}{dp[i - 1][k]}, S[i] == 'D'
dp[i][j]=k=j∑i−1dp[i−1][k],S[i]==′D′
d
p
[
i
]
[
j
]
=
∑
k
=
j
i
−
1
d
p
[
i
−
1
]
[
k
]
,
i
f
S
[
i
]
=
=
′
I
′
dp[i][j]=\sum_{k=j}^{i-1}{dp[i - 1][k]}, if S[i] == 'I'
dp[i][j]=k=j∑i−1dp[i−1][k],ifS[i]==′I′
以DID为例,则可列表格如下:
Si/j | 0 | 1 | 2 | 3 |
---|---|---|---|---|
null | 1 | X | X | X |
D | 1 | 0 | X | X |
I | 0 | 1 | 1 | X |
D | 2 | 2 | 1 | 0 |
最后遍历最后一行加和就是结果。
代码
代码如下:
public int numPermsDISequence(String S) {
int[][] dp = new int[S.length() + 1][S.length() + 1];
char[] str = (" " + S).toCharArray();
dp[0][0] = 1;
for (int i = 1; i < str.length; ++ i){
for (int j = 0; j <= i; ++ j){
if (str[i] == 'D'){
for (int k = j; k < i; ++ k){
dp[i][j] += dp[i - 1][k];
dp[i][j] = dp[i][j] % 1000_000_007;
}
}else{
for (int k = 0; k < j; ++ k){
dp[i][j] += dp[i - 1][k];
dp[i][j] = dp[i][j] % 1000_000_007;
}
}
}
}
int ans = 0;
for (int i = 0;i < str.length; ++ i){
ans += dp[str.length - 1][i];
ans %= 1000_000_007;
}
return ans;
}