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/