LeetCode 903: DI序列的有效排列

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];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值