We are given S
, a length n
string of characters from the set {'D', 'I'}
. (These letters stand for "decreasing" and "increasing".)html
A valid permutation is a permutation P[0], P[1], ..., P[n]
of integers {0, 1, ..., n}
, such that for all i
:git
S[i] == 'D'
, then P[i] > P[i+1]
, and;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
.github
Example 1:数组
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)
Note:spa
1 <= S.length <= 200
S
consists only of characters from the set {'D', 'I'}
.
这道题给了咱们一个长度为n的字符串S,里面只有两个字母D和I,分别表示降低 Decrease 和上升 Increase,意思是对于 [0, n] 内全部数字的排序,必须知足S的模式,好比题目中给的例子 S="DID",就表示序列须要先降,再升,再降,因而就有那5种状况。题目中提示告终果可能会是一个超大数,让咱们对 1e9+7 取余,经验丰富的刷题老司机看到这里就知道确定不能递归遍历全部状况,编译器估计都不容许,这里动态规划 Dynamic Programming 就是不二之选,可是这道题正确的动态规划解法实际上是比较难想出来的,由于存在着关键的隐藏信息 Hidden Information,若不能正确的挖掘出来(山东布鲁斯特挖掘机专业了解一下?),是不太容易解出来的。首先来定义咱们的 DP 数组吧,这里你们的第一直觉多是想着就用一个一维数组 dp,其中 dp[i] 表示范围在 [0, i] 内的字符串S的子串能有的不一样序列的个数。这样定义的话,就没法写出状态转移方程,像以前说的,咱们忽略了很关键的隐藏信息。先来想,序列是升是降到底跟什么关系最大,答案是最后一个数字,好比咱们如今有一个数字3,当前的模式是D,说明须要降低,因此可能的数字就是 0,1,2,但若是当前的数字是1,那么还要降低的话,那么貌似就只能加0了?其实也不必定,由于这道题说了只须要保证升降模式正确就好了,数字之间的顺序关系其实并不重要,举个例子来讲吧,假如咱们如今已经有了一个 "DID" 模式的序列 1032,假如咱们还想加一个D,变成 "DIDD",该怎么加数字呢?多了一个模式符,就多了一个数字4,显然直接加4是不行的,实际是能够在末尾加2的,可是要先把原序列中大于等于2的数字都先加1,即 1032 -> 1043,而后再加2,变成 10432,就是 "DIDD" 了。虽然咱们改变了序列的数字顺序,可是升降模式仍是保持不变的。同理,也是能够加1的,1032 -> 2043 -> 20431,也是能够加0的,1032 -> 2143 -> 21430。可是没法加3和4,由于 1032 最后一个数字2很很重要,全部小于等于2的数字,均可以加在后面,从而造成降序。那么反过来也是同样,若要加个升序,好比变成 "DIDI",猜也猜的出来,后面要加大于2的数字,而后把全部大于等于这个数字的地方都减1,好比加上3,1032 -> 1042 -> 10423,再好比加上4,1032 -> 1032 -> 10324。code
经过上面的分析,咱们知道了最后一个位置的数字的大小很是的重要,不论是要新加升序仍是降序,最后的数字的大小直接决定了能造成多少个不一样的序列,这个就是本题的隐藏信息,因此咱们在定义 dp 数组的时候必需要把最后一个数字考虑进去,这样就须要一个二维的 dp 数组,其中 dp[i][j] 表示由范围 [0, i] 内的数字组成且最后一个数字为j的不一样序列的个数。就拿题目中的例子来讲吧,由数字 [0, 1, 2, 3] 组成 "DID" 模式的序列,首先 dp[0][0] 是要初始化为1,以下所示(括号里是实际的序列):orm
dp[0][0] = 1 (0)
htm
而后须要加第二个数字,因为须要降序,那么根据以前的分析,加的数字不能大于最后一个数字0,则只能加0,以下所示:blog
加0: ( dp[1][0] = 1 ) 0 -> 1 -> 10
而后须要加第三个数字,因为须要升序,那么根据以前的分析,加的数字不能小于最后一个数字0,那么实际上能够加的数字有 1,2,以下所示:排序
加1: ( dp[2][1] = 1 ) 10 -> 20 -> 201 加2: ( dp[2][2] = 1 ) 10 -> 10 -> 102
而后须要加第四个数字,因为须要降序,那么根据以前的分析,加的数字不能大于最后一个数字,上一轮的最后一个数字有1或2,那么实际上能够加的数字有 0,1,2,以下所示:
加0: ( dp[3][0] = 2 ) 201 -> 312 -> 3120 102 -> 213 -> 2130 加1: ( dp[3][1] = 2 ) 201 -> 302 -> 3021 102 -> 203 -> 2031 加2: ( dp[3][2] = 1 ) 102 -> 103 -> 1032
这种方法算出的 dp 数组为:
1 0 0 0 1 0 0 0 0 1 1 0 2 2 1 0
最后把 dp 数组的最后一行加起来 2+2+1 = 5 就是最终的结果,分析到这里,其实状态转移方程已经不可贵到了,根据前面的分析,当是降序时,下一个数字不小于当前最后一个数字,反之是升序时,下一个数字小于当前最后一个数字,因此能够写出状态转移方程以下所示:
if (S[i-1] == 'D') dp[i][j] += dp[i-1][k] ( j <= k <= i-1 ) else dp[i][j] += dp[i-1][k] ( 0 <= k < j )
解法一:
class Solution { public: int numPermsDISequence(string S) { int res = 0, n = S.size(), M = 1e9 + 7; vector<vector<int>> dp(n + 1, vector<int>(n + 1)); dp[0][0] = 1; for (int i = 1; i <= n; ++i) { for (int j = 0; j <= i; ++j) { if (S[i - 1] == 'D') { for (int k = j; k <= i - 1; ++k) { dp[i][j] = (dp[i][j] + dp[i - 1][k]) % M; } } else { for (int k = 0; k <= j - 1; ++k) { dp[i][j] = (dp[i][j] + dp[i - 1][k]) % M; } } } } for (int i = 0; i <= n; ++i) { res = (res + dp[n][i]) % M; } return res; } };
咱们还能够换一种形式 DP 解法,这里的 dp 数组在定义上跟以前的略有区别,仍是用一个二维数组,这里的 dp[i][j] 表示由 i+1 个数字组成且第 i+1 个数字(即序列中的最后一个数字)是剩余数字中(包括当前数字)中第 j+1 小的数字。好比 dp[0][0],表示序列只有1个数字,且该数字是剩余数字中最小的,那就只能是0。再好比,dp[1][2] 表示该序列有两个数字,且第二个数字是剩余数字中第三小的,那么序列只能是 32,由于剩余数字为 0,1,2(包括最后一个数字),这里2就是第三小的。有些状况序列不惟一,好比 dp[1][1] 表示该序列有两个数字,且第二个数字是剩余数字中第二小的,此时的序列就有 31(1是 0,1,2 中第二小的)和 21(1是 0,1,3 中第二小的)两种状况。搞清楚了 dp 的定义以后,再来推导状态转移方程吧,对于 dp[0][j] 的状况,十分好判断,由于只有一个数字,并不存在升序降序的问题,因此 dp[0][j] 能够都初始化为1,以下所示(括号里是实际的序列):
dp[0][3] = 1 (3) dp[0][2] = 1 (2) dp[0][1] = 1 (1) dp[0][0] = 1 (0)
而后须要加第二个数字,因为须要降序,那么根据以前的分析,新加的数字不多是第四小的,因此不可能出现 dp[1][3] 为正数,由于这表示有两个数字,且第二个数字是剩余数字中的第四小,总共就四个数字,第四小的数字就是最大数字,因为是降序,因此第二个数字要小于第一个数字,这里就矛盾了,因此 dp[1][3] 必定为0,而其他的确实能够从上一层递推过来,具体来讲,对于 dp[1][j],须要累加 dp[0][k] ( j < k <= n-1 ):
dp[1][2] = dp[0][3] = 1 (32) dp[1][1] = dp[0][3] + dp[0][2] = 2 (31, 21) dp[1][0] = dp[0][3] + dp[0][2] + dp[0][1] = 3 (30, 20, 10)
而后须要加第三个数字,因为须要升序,那么根据以前的分析,此时已经有两个数字了,新加的第三个数字只多是剩余数字的第一小和第二小,即只有 dp[2][0] 和 dp[2][1] 会有值,跟上面类似,其也是由上一层累加而来,对于 dp[2][j],须要累加 dp[1][k] ( 0 <= k <= j ):
dp[2][1] = dp[1][1] + dp[1][0] = 5 (312, 213, 302, 203, 103) dp[2][0] = dp[1][0] = 3 (301, 201, 102)
最后再加第四个数字,因为须要降序,那么根据以前的分析,此时已经有三个数字了,新加的第四个数字只多是剩余数字的第一小,即只有 dp[3][0] 会有值,跟上面类似,其也是由上一层累加而来,对于 dp[3][j],须要累加 dp[2][k] ( j< k <= n-1 ),这里有值的只有 dp[2][1]:
dp[3][0] = dp[2][1] = 5 (3120, 2130, 3021, 2031, 1032)
这种方法算出的 dp 数组为:
1 1 1 1 3 2 1 0 3 5 0 0 5 0 0 0
这种方法算出的最终结果必定是保存在 dp[n][0] 中的,分析到这里,其实状态转移方程已经不可贵到了,以下所示:
if (S[i] == 'D') dp[i+1][j] = sum(dp[i][k]) ( j < k <= n-1 ) else dp[i+1][j] = sum(dp[i][k]) ( 0 <= k <= j )
解法二:
class Solution { public: int numPermsDISequence(string S) { int n = S.size(), M = 1e9 + 7; vector<vector<int>> dp(n + 1, vector<int>(n + 1)); for (int j = 0; j <= n; ++j) dp[0][j] = 1; for (int i = 0; i < n; ++i) { if (S[i] == 'I') { for (int j = 0, cur = 0; j < n - i; ++j) { dp[i + 1][j] = cur = (cur + dp[i][j]) % M; } } else { for (int j = n - 1 - i, cur = 0; j >= 0; --j) { dp[i + 1][j] = cur = (cur + dp[i][j + 1]) % M; } } } return dp[n][0]; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/903
参考资料:
https://leetcode.com/problems/valid-permutations-for-di-sequence/