使字符串有序的最少操作次数
题意
给你一个字符串 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;
}
};