字典序问题

字典序问题

字典序简单介绍

碰到过很多求关于字典序的问题,一直都是用的c++的STL库中的函数水过的,今天终于有机会,算是对字符串字典序问题的一个总结吧。
如果已知一个字符串“ABC”,为了更直观,我们把建立如下映射:

A->1
B->2
C->3

我们把这三个字母的所有排列都写出来,有:

序号字符串映射数字
1ABC123
2ACB132
3BAC213
4BCA231
5CAB312
6CBA321

如上,我们会发现,我们最后映射出相应的数字结果是从小到大排列的。而我们上面的排列顺序,顾名思义,就像字母在字典里的排列顺序一样,所以就被叫做按字典序排列。

求下一个字典序

事实上我们如果已知一串字符串,是有办法直接根据它求出下一个字典序的,下面我们将要介绍最简单的求下一个字符串的方法。

假设我们已知一字符串 s =“BAC”:

  • 首先,我们从后向前找到第一个满足字典序s[small] < s[small + 1]的位置,找不到就说明已经是最后一个字符串了。注意必须是第一个!这里我们找到了 A < C,即small = 1。
  • 从s[small + 1]开始,找到比s[small]大的最小的字符,标记其所在为s[big]。如果有多个一样的,根据您的个人喜好任选一个就好了,因此我们找到了C,即big = 2。只要第一步完成,这步一定会找到的,可以自习想一想为什么。
  • 交换s[small]和s[big],现在我们的串变成了“BCA”。
  • 把s[small+1]开始到结尾的字符串按照字典序从小到大排列,新得到的字符串就是下一个字典序了。我们这里字符串仍然是“BCA”。

代码如下:

@Frosero
//求下一个字典序
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

bool next_permutation(string &s){  //如果无下一个字典序,返回false
    int small = -1,big;
    for(string::size_type i = s.size()-2;i >= 0;i--){   //第一步
        if(s[i] < s[i+1]){
            small = i;
            break;
        }
    }
    if(small == -1) return false;
    big = small + 1;
    for(string::size_type i = small + 1;i<s.size();i++){  //第二步
        if(s[i] > s[small] && s[i] < s[big]) big = i;
    }
    swap(s[small],s[big]);
    for(string::size_type i = small + 1;i < s.size();i++){  //最后排序,为简洁这里用冒泡^.^
        for(string::size_type j = i + 1;j < s.size();j++){
            if(s[i] > s[j]) swap(s[i],s[j]);
        }
    }
    return true;
}

int main(){
    string str = "ABCDEFGH";
    while(next_permutation(str)){
        cout<<str<<endl;
    }
    return 0;
}

快速定位字典序算法

上诉方法虽然给了我们一个求一个字典序的朴素方法,但是在某些情况下并不适用。
比如我们要准确找到第N个字典序的排列是什么的时候,我们不可能傻傻的一个个的序列数过去。就算是一个长度为20的无重复字符的序列,它的排列方式也有20!即2432902008176640000种!!估计数完以后我们都老了 = =。
怎么办呢,其实我们有非常快的方法直接求得,这就要用到我们曾经学过的排列组合,我们思想是逐步确定每一位应该是什么。
直接上代码:

@Frosero
//快速定位第n字典序算法
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

inline long long mulcal(long long n){
    if(n == 0) return 1;
    if(n == 1) return n;
    else return n*mulcal(n-1);
}

void Kth_permutation(string &s,long long n){        //求第n个字典序的序列
    long long alp_cnt[30],sum = 0;                      //alp_cnt[i]表示字符‘A’+i的个数
    memset(alp_cnt,0,sizeof(alp_cnt));
    for(string::size_type i = 0;i < s.size();i++) alp_cnt[s[i]-'A']++;
    for(string::size_type sz = 0;sz < s.size();sz++){
        for(int i = 0;i < 27;i++)if(alp_cnt[i]){        //尝试在第sz位填上字符‘A’+i
            long long now = 0,alps = -1;
            for(int k = 0;k < 27;k++)if(alp_cnt[k]){
                alps += alp_cnt[k];
            }
            now = mulcal(alps);                         //根据排列组合求此时剩余字符的不同排列数
            for(int k = 0;k < 27;k++)if(alp_cnt[k]){
                if(k == i) now /= mulcal(alp_cnt[k] - 1);
                else now /= mulcal(alp_cnt[k]);
            }
            if(sum + now >= n){                     //如果发现可填,则相应填上字符,并在alp_cnt中减去相应字符
                alp_cnt[i]--;
                s[sz] = 'A' + i;
                break;
            }
            sum += now;
        }
    }
}

int main(){
    string str = "ABCDEFG";
    Kth_permutation(str,2);
    cout << str << endl;
    return 0;
}

拓展

我们还存在一些问题用上诉方法很难解决

  • 给定一个字符串的排列,求所有它的排列中比这个串字典序小的有多少
  • 某些特殊情况下,比上诉第二个方法更优秀的求第K字典序字符串

详情请走传送门:萌萌哒请点我~@Frosero

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值