《计算机算法设计与分析》中第一章的第二道题-字典序问题。
题目如下:
在数据加密和数据压缩中常需要对特殊的字符串进行编码。给定的字母表A由26个小写字母组成。该字母表产生的升序字符串中字母从左到右出现的次序与字母在字母表中出现的次序相同,且每个字符最多出现1次。例如,a,b,ab,bc,xyz等字符串都是升序字符串。现在对字母表中产生的所有长度不超过6的升序字符串,计算它在字典中的编码。
1 | 2 | ... | 26 | 27 | 28 | ... |
a | b | ... | z | ab | ac | ... |
样例输入:
2
a
b
样例输出:
1
2
此题是一个排列组合中组合的一个应用,也就是从26个字母中任取n个字母形成一个组合,对组合来说,也就是两种排列相结合形成的,此题中第一种排列就是从26个字母中任取n个字母的排列,一共有26!/(26-n)!种;第二种排列也就是n个字母之间的排列,一共有n!种,第一种/第二种就是26!/(26-n)1*n! 种,见下图详解:
此方法就是本题的关键所在,看懂此方法后此题就很简单了。
解题步骤:我们假设输入的字符长度为len,那么我们可以先找到长度小于len的升序排序,也就是按照上面的方法将字符长度为1一直到len-1的升序排序的数量先求出来;接下来再处理长度为len的个数就可以了。
对于长度为len的字符串,从不是以字符串首字母开头开始找,我们从字母a为开头的字符串开始找,先确定第一个字母在确定第二个字母,一直到全部确定,将这些相加就可以了。
详细步骤:由于字符串是升序的
1、所以从第一个字母从a开始找,如果第一个字母不是a,则从剩下的26-1个字母里面任取n-1个字母,进行组合,(方法就和前面方法的原理是一样的)得出的值与前面长度从1到len-1的编码值相加;
2、继续从字母b开始找,如果字母b也不是,根据第一步一样,从26-2个字母中任取n-2个字母,进行组合,得出的值与前面得出的编码值相加;
3、假如字母c为第一个字母,则我们可以开始找第二个字母,第二个字母从d开始找,也就是从前一个字母的后面的第一个字母继续找,和上面前两步一样操作,
直到找到最后一个字母,此时的编码值是此字符串按照顺序的前一个字符串的编码值,在加1就是此字符串的编码值。
代码如下所示:
#include<iostream>
#include<string>
using namespace std;
int Code(int m, int n)
{
int Max=1, Min=1;//Max记录26个中取n个的结果,Min记录从n个排列的结果
for (int j = m; j > m - n; j--)//得出Max
{
Max *= j;
}
for (int i = 1; i <= n; i++)//得出Min
{
Min *= i;
}
return Max / Min;
}
int main()
{
string str;//保存字符串
int len;//记录字符串长度
int sum;//编码值
int temp;//中间转换值
int num[10];//将每个字符转换一个整数保存在此数组中a为1,z为26
int n;//行数
cout << "请输入要输入的行数:";
cin >> n;
while (n--)
{
cin >> str;
sum = 0;//起始值为0
len = str.length();//获取字符串长度
//得出字符长度从1到len-1的编码值
for (int i = 1; i < len; i++)
{
sum += Code(26, i);
}
//得出字符串中每个字符a为1,b为2...z为26
for (int i = 0; i < len; i++)
{
num[i] = str[i] - 96;
}
//得出长度为len但还不是该字符串首字母开头的编码值
temp = 1;
for (int i = len; i>0; i--)
{
for (int j = temp; j < num[len - i]; j++)//起始是num[0],每次循环结束可以找到一个字母
{
sum += Code(26 - j, i - 1);
}
temp = num[len - i] + 1;//继续找下一位字母
}
cout << sum + 1 << endl;//得出编码值
}
return 0;
}
样例操作结果如下所示: