使字符串有序的最少操作次数

题意

给你一个字符串 s (下标从 0 开始)。你需要对 s 执行以下操作直到它变为一个有序字符串:
找到 最大下标 i ,使得 1 <= i < s.length 且 s[i] < s[i - 1] 。
找到 最大下标 j ,使得 i <= j < s.length 且对于所有在闭区间 [i, j] 之间的 k 都有 s[k] < s[i - 1] 。
交换下标为 i - 1​​​​ 和 j​​​​ 处的两个字符。
将下标 i 开始的字符串后缀反转。
请你返回将字符串变成有序的最少操作次数。由于答案可能会很大,请返回它对 109 + 7 取余 的结果。

解析

实质上是求一个全排列,使其最终有序。例如cba->cab->bca->bac->acb->abc
本质上是组合计数。确定具有相同前缀,比当前字符小的排列数目。如cba以cb为前缀的只有cba,以c为前缀比b小的前缀只有a,a后面的位置求组合数是C(1,1)。
设字符串p,那么p后的可能方案满足字符s<p,每个s后的组合数为C(z_a,a1)C(z_a-a1,a2)()...()
代码如下:

#define ll long long
class Solution {
public:
//求全排列
    const int mod=1e9+7;
    int c[3050][3050],cnt[26];
    void init(int n)
    {   
        c[0][0]=1;
        for(int i=1;i<=n;i++){
            c[i][0]=c[i][i]=1;
            for(int j=1;j<i;j++) c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
        }
    }
    ll find(int n,int m)
    {
        if(n==0) return 1ll;
        if(m==0) return 1ll;
        if(m>n)  return 1ll;
        return c[n][m];
    }
    int makeStringSorted(string s) {
        int n=s.size();
        init(n);
        ll ans=0;
        for(int i=n-1;i>=0;i--){
            int val=s[i]-'a';
            cnt[val]++;
            for(int c=val-1;c>=0;c--){ //当前位填c,c<val 在剩余的字母中寻找填入
                if(cnt[c]==0) continue;
                cnt[c]-=1;
                int left=n-i-1;
                ll cur=1;
                for(int j=0;j<26;j++){ //剩余的位置填入a-z的字母
                    cur=cur*find(left,cnt[j])%mod;
                    left-=cnt[j];
                }
                cnt[c]+=1;
                ans=(ans+cur)%mod;
            }
        }
        return ans;
    }
};
原文地址:https://www.cnblogs.com/flightless/p/14715736.html