PTAL1-050 倒数第N个字符串(15 分)算法深度解析

给定一个完全由小写英文字母组成的字符串等差递增序列,该序列中的每个字符串的长度固定为 L,
从 L 个 a 开始,以 1 为步长递增。例如当 L 为 3 时,序列为 
{ aaa, aab, aac, ..., aaz, aba, abb, ..., abz, ..., zzz }。这个序列的倒数第27个字符串就是 zyz。
对于任意给定的 L,本题要求你给出对应序列倒数第 N 个字符串。
输入格式:
输入在一行中给出两个正整数 L(2 <= L <= 6)和 N(<= 10^5^)。
输出格式:
在一行中输出对应序列倒数第 N 个字符串。题目保证这个字符串是存在的。
输入样例:
3 7417
输出样例:

pat

本题难点在于找到题目的规律,本人第一次做此题时想了半天没处理好,后网搜,整理并详细解释如下:

此题最暴力的解法是逐个存储,但规模稍微大一点就不行,这是有规律的序列,运用规律解题才是真正的解法。那么规律是什么?我们发现,最后一个字母每隔25个字母或者串出现一次,即下标每增加26出现一次,比如aaa,最后一个a在第27个串出现,有了规律,便容易得到公式,从数学角度来看就是通项公式。则假设你要求第n个串,“公式”为保留n%26,进行n/=26,重复串长L次,类似于求十进制数每位上的数字,十进制数的特点是末位上的数字每个9个数字出现一次,即下标每增加10出现一次,再高一位是在末位乘10后,此位的下标每增加100出现一次,即在末位的基础上“每增10”出现一次。所以如果你要得到10进制数的各个位,你必须先保留模10结果,再将原数除以10,循环。此思想用在本题亦是如此,相当于得到“26进制数”的各个位,每个位对应一个字母,这里是将10进制数看成“26进制数”,而得到各个位,各个位的范围在0-25之间,如果要转成字母,只需将a的ASC码加上各个数字即可。需要注意两点,如下:

1.10进制数代表在串的下标,所以你按照“下标每增26字母重复”的规律,用“模26的公式”得到的就是这个10进制数下标对应的串。

2.由于模26的结果在0-25之间,如果为0,你得到a,由此可以推出,下标是从0开始的。所以结合本题,下标,即26的L次-N是从0开始的。

你还需要注意一点:倒数第N个串,N是从1开始的,因为下标是从0开始的,故倒数第N个串就是26的L次-N的下标对应的串。譬如,有下标0 1 2 3,长度为4,N=2,则4-2=2的下标对应的串就是此序列倒数第2个串。如果下标是从1开始,那么有1 2 3 4,N=2,则4-2+1=3下标对应的串为倒数第2个串。

本题一般解法用的方法:pow函数,形参类型为double,返回类型为double,若所求下标为M,double型,则M=pow((double)26,(double)L)-N;再将M转为int,这些操作应该适用于几乎所有c/c++编译器,至少轻量级IDE CFREE支持。如果你将这些操作改为 int M=pow((double)26,(double)L)-N,则当L=2,N=649,即M逻辑上为27时,CFREE运行结果为26,但是如果你本题算法正确,提交至PTA平台,g++编译器可以使你的结果运行正确。如果你改为int M=pow(26,L)-N,则CFREE编译器会报错,因为它无法在函数入口将形参自动从int转为double。而上述假设操作在VS环境中均可正确执行,故一般而言,写第一个假设情况稳定。

分析完算法,以下是具体代码:

#include<iostream>
#include<cmath>
using namespace std;
void Find(int *a,int L,int n)
{
	int t=L;
	while(t--)
	{
		a[t]=n%26;//每个字母每隔25个出现一次,故模26,范围0-25 
		n/=26;
	}
	for(int i=0;i<L;i++)
	cout<<(char)('a'+a[i]);//转换为char,否则输出的是数字 
}
int main()
{
	int L,N;
	int a[6];
	cin>>L>>N;//注意:N从0开始 
	double M=pow((double)26,(double)L)-N;//相减得到从0开始的序号M 
	Find(a,L,(int)M);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37729102/article/details/80888228