字典序问题
字典序简单介绍
碰到过很多求关于字典序的问题,一直都是用的c++的STL库中的函数水过的,今天终于有机会,算是对字符串字典序问题的一个总结吧。
如果已知一个字符串“ABC”,为了更直观,我们把建立如下映射:
A->1
B->2
C->3
我们把这三个字母的所有排列都写出来,有:
序号 | 字符串 | 映射数字 |
---|---|---|
1 | ABC | 123 |
2 | ACB | 132 |
3 | BAC | 213 |
4 | BCA | 231 |
5 | CAB | 312 |
6 | CBA | 321 |
如上,我们会发现,我们最后映射出相应的数字结果是从小到大排列的。而我们上面的排列顺序,顾名思义,就像字母在字典里的排列顺序一样,所以就被叫做按字典序排列。
求下一个字典序
事实上我们如果已知一串字符串,是有办法直接根据它求出下一个字典序的,下面我们将要介绍最简单的求下一个字符串的方法。
假设我们已知一字符串 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