S
是由'D'
和'I'
构成的长度为 n 的字符串,P
是整数0 : n的一个排列,满足:
对任意的 i,若S[i]='D'
,则P[i]>P[i+1]
;若S[i]='I'
,则P[i]<P[i+1]
。
求满足条件的P的排列的个数,输出结果取1e9+7
的模。
暴力的解法是用next_permutation()
枚举0 : n的所有排列,若符合条件计数+1,运行结果超时。
LeetCode 903的官方题解没有看懂,本文的思路基于https://blog.csdn.net/AzureoSky/article/details/82722114。
个人理解动态规划的关键在于找到递推公式,而递推的关键在于确定DP[i][j]
的意义。定义DP[i][j]
:将整数 0 : i(i+1个数)根据S
的前 i 个字符排列,且 j (0 ≤ j ≤ i) 排在末尾(即第 i+1位)的排列的个数。接下来需要思考DP[i][j]
和其他状态之间的关系,在这之前我们来看这样一组示例:
已知S="IDDID"
,满足DP[4][3]
一个排列P
是1 4 2 0 3,在寻找满足DP[5][3]
的排列时,将排列P
中大于等于3的数+1,并把3置于末尾,得到1 5 2 0 4 3,是一个既满足前4个字符、又符合第5个字符的排列。可以推得,满足DP[4][3]
、DP[4][4]
的排列,根据上述方法在排列的末尾添加3,即可得到满足DP[5][3]
的排列。因此,当S[i-1]=='D'
时,DP[i][j]=DP[i-1][j]+DP[i-1][j+1]+...+DP[i-1][i-1]
。
若S="IDDII"
,在寻找满足DP[5][4]
的排列时,同样的方法将4置于排列末,得到的1 5 2 0 3 4,符合条件。可以推得,满足DP[4][3]
、DP[4][2]
、DP[4][1]
、DP[4][0]
的排列,根据上述方法在排列的末尾添加4,即可得到满足DP[5][4]
的排列。因此,当S[i-1]=='I'
时,DP[i][j]=DP[i-1][0]+DP[i-1][1]+...+DP[i-1][j-1]
。
可以看到,每一步递推的结果DP[i][j]
只和S
中第 i 个字符有关,这是动态规划“子问题独立”的体现。基于上述递推公式得到DP二维矩阵,DP[n]
的和即满足S
的排列的个数。该算法的时间复杂度是O(n^3)。
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int numPermsDISequence(string S) {
int n = S.size();
int mod = 1e9+7;
vector<vector<int>> DP(n + 1, vector<int>(n + 1, 0));
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; ++k) {
DP[i][j] += DP[i - 1][k];
DP[i][j] %= mod;
}
}
if (S[i - 1] == 'I') {
for (int k = 0; k < j; ++k) {
DP[i][j] += DP[i - 1][k];
DP[i][j] %= mod;
}
}
}
}
int sum = 0;
for(int i = 0;i <= n;++i) {
sum += DP[n][i];
sum %= mod;
}
return sum;
}
若定义DP[i][j]
:将整数 0 : i(i+1个数)根据S
的前 i 个字符排列,以 0、1、…、j-1 或 j (0 ≤ j ≤ i) 排在末尾(即第 i+1位)的排列的个数和。算法的时间复杂度将降至O(n^2)。
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int numPermsDISequence(string S) {
int n = S.size();
int mod = 1e9+7;
vector<vector<int>> DP(n + 1, vector<int>(n + 1, 0));
DP[0][0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j <= i; ++j) {
//这一步的运算思路可以和第一种方法的DP矩阵对比来看
if (S[i - 1] == 'D') {
DP[i][j] = (j == 0 ? 0 : DP[i][j - 1] - DP[i - 1][j - 1]) + DP[i - 1][i - 1];
}
if (S[i - 1] == 'I') {
DP[i][j] = j == 0 ? 0 : DP[i][j - 1] + DP[i - 1][j - 1];
}
if(DP[i][j] < 0)
DP[i][j] += mod; //上一步的取余可能造成这一步的运算结果出现负值
DP[i][j] %= mod;
}
}
return DP[n][n];
}