一开始我的思路是按照进制转换,a~z的进制是26,所以ab = 27 * 1 + 1 * 2 = 29
但题目有个条件,即字典序排序,也就是不可能出现诸如“aa”、“ba”的情况,这样就会使得后面的编码向前移动,处理起来很麻烦
解决方案
求某个字符串的编码,可以分析在此之前的字符串有多少个,每个字符串占一个编码,那么个数和编码就一一对应了
以求字符串“cefh”的编码为例:
① 先求出位数< 4 的字符串有多少,即位数为1,位数为2,以及位数为3的总个数
通过组合数来求(因为字典序,所以没有排序需要)
② 接下来求位数 = 4 的字符串从开始到 “cegm” 的个数
首先从第一位看起,统计从 “a” 到 “c”,先是“a * * *”的个数,得到 ,然后统计“b * * * ”,得到
这样的话,我们就把第一位固定到了 “c”,变成了“c * * * ”,接下来就看第二位
因为升序,第n位的起点至少是第n-1位的数+ 1,因此第二位的起点是 c + 1=d
下面的步骤与第一步一样,从“d”到“e”,只需统计 “c d * *”的个数,
这样的话,我们就把第二位固定到了 “e”,变成了“c e * *”,接下来看第三位
第三位的起点是 e + 1=f,从“f”到“g”,得到,这样就把第三位固定到了 “g”,变为“c e g *”
最后第四位的起点 g + 1 = h,从“h”到“m”,得到
可以发现,到了第 i 位时,只剩下 len - i - 1 位可以自由放数字,之所以减去1,是因为如果剩下所有位都自由放字母,就会超过所求的字符串
例如“abcde”,到了第 2 位“b”,那么就只剩下“c” 和“d”的位置可以自由放字母,如果“e”的位置也随便放,那么就会超过编码
同时,若第 i 位的字母编码为alpha,因为升序要求,只剩下 26 - alpha 个字母可以选择
因此,就是在 26 - alpha 个字母当中,选择出 len - i - 1 个放到位置上(组合数)
设计步骤
一、计算组合数
二、计算小于位数len
的字符串个数
三、计算位数等于 len
的字符串从开始到所求字符串之间的个数
for 字符串的第1位 -> len - 1位:
for 当前位的起点字母 -> 当前位字母的前一个字母:
sum += C(剩余可选字母个数, 剩余位数)
end
确定下一位的起点字母(即当前位字母的后一个字母)
end
四、将输入的字符串编码
比如,给字符串“abz”,则编码为“1 2 26”,借助数组实现
这样是方便之后的比较
代码实现
#include <iostream>
#include <string.h>
using std::cin;
using std::cout;
using std::endl;
using std::string;
int C(int n, int m) {
int sum1 = 1, sum2 = 1;
for (int i = 2; i < m + 1; ++i)
sum2 *= i;
for (int i = n; i > n - m; --i)
sum1 *= i;
return sum1 / sum2;
}
int main()
{
int n;
cin >> n;
while (n--) {
string code;
cin >> code;
int len = code.length(), sum = 0;
int code_num[len];
// 1. 将字符串进行编码
for (int i = 0; i < len; ++i)
code_num[i] = code[i] - 96;
// 2. 统计位数小于len的个数
for (int i = 1; i < len; ++i)
sum += C(26, i);
// 3. 统计位数等于len的字符串到所求之间的个数
int start = 1; // 第 x 位的起点,初始化为1
for (int i = 0; i < len; ++i) {
for (int j = start; j < code_num[i]; ++j) {
sum += C(26- j, len - i - 1);
// 26 - j 表示剩下还有多少字母可以选择
// len - i - 1 表示剩下多少位置可以放字母
}
start = code_num[i] + 1; // 更新下一位的起点,因为升序,所以至少 + 1
}
cout << sum << endl;
}
return 0;
}
参考:字典序问题(算法实现—python)_校验字典序的代码-CSDN博客
另解: