1.题目
「外观数列」是一个数位字符串序列,由递归公式定义:
countAndSay(1) = "1"
countAndSay(n) 是 countAndSay(n-1) 的行程长度编码。
行程长度编码(RLE)是一种字符串压缩方法,其工作原理是通过将连续相同字符(重复两次或更多次)替换为字符重复次数(运行长度)和字符的串联。例如,要压缩字符串 "3322251" ,我们将 "33" 用 "23" 替换,将 "222" 用 "32" 替换,将 "5" 用 "15" 替换并将 "1" 用 "11" 替换。因此压缩后字符串变为 "23321511"。
给定一个整数 n ,返回 外观数列 的第 n 个元素。
2.示例
示例 1:
输入:n = 4
输出:"1211"
解释:
countAndSay(1) = "1"
countAndSay(2) = "1" 的行程长度编码 = "11"
countAndSay(3) = "11" 的行程长度编码 = "21"
countAndSay(4) = "21" 的行程长度编码 = "1211"
示例 2:
输入:n = 1
输出:"1"
解释:
这是基本情况。
提示:
1 <= n <= 30
3.分析
(1)第一种:非递归
使用3层循环:
第一层循环是用来从i==2到i==n的循环,每一次循环结束就可以得到n==i时的外观数列;
第二层循环是用来遍历上一轮得到的外观数列并生成新的外观数列;
第三层循环是用来计算上一轮得到的外观数列中每一位元素,连续存在的个数;
(2)第二种:递归
从i==n递归到i==1,然后再从i==1返回到i==n。每一次返回的是i的外观数列。
每一层递归最重要的是用一个双层循环构造i的外观数列。
第一层循环遍历返回的外观数列。
第二层循环计算外观数列重每个元素连续存在的个数,并构造新的外观数列。
最后得到新的外观数列后,就返回给上一层。
4.代码
(1)非递归
char* countAndSay(int n) {
//起始空间大小为2
char *result = (char*)malloc(2);
*(result + 0) = '1';//当n==1时,它的外观数列时“1”
*(result + 1) = '\0';//开辟的对空间一定要多一个空间用来存储'\0',否则后面的遍历会出错
//如果n==1,直接返回外观数列"1"
if (n == 1)
{
return result;
}
//如果n>=2,就通过如下循环进行计算。
for (int i = 2; i <= n; i++)
{
//保存原有的堆空间
char *temp_result=temp_result = result;
//这里开辟一个新的空间用于存储新的外观数列,这里起始空间大小只有1个字节,存储'\0'
result = (char*)malloc(1);
*result = '\0';
size_t room = 2;//这个变量用来记录开辟新空间的空间大小,初始值为2
//这个双层for循环就是用来计算每个元素的个数,并把得到的个数和元素保存到新的堆空间中
//j用来遍历原来的外观数列;m用来记录每个元素应该保存到新的堆空间的位置;room表示每一次循环时,开辟新空间的大小
for (int j = 0, m = 0; *(temp_result + j) != '\0'; room+=2, m += 2)
{
//得到每个元素的个数
int count = 1;
for (int t = j + 1; *(temp_result + t) != '\0'; t++)
{
if ((*(temp_result + t)) == (*(temp_result + j)))
{
count++;
}
else break;
}
//得到*(temp_result + j)元素的个数后,就要开辟新的空间,这个新的空间不仅要保存原来堆空间中已经存在的元素,还要存储*(temp_result + j)和它的个数,因此
//每次m+=2;
result = (char*)realloc(result, room);
//存储新的元素及其个数
*(result + m) = '0' + count;
*(result + m + 1) = *(temp_result + j);
//每次向后移动的步数肯定是要把连续相等的元素全部跳过,因此是j+=count;
j += count;
}
room -= 1;//这里romm-=1,是因为上一个for循环结束时多加了一个2,因此要减一
//再申请一个新的空间,这个空间比原空间大一,目的是要在元空间中的外观数列末尾加入一个'\0',否则下一轮循环会出错
result = (char*)realloc(result, room);
*(result + room-1) = '\0';
//释放上一个存储外观数列的堆空间
free(temp_result);
}
//返回存储外观数列的堆空间。
return result;
}
(2)递归
#define MAX_STR_LEN 10000 //每次申请的堆空间大小,用来保存每一次迭代的外观数列
//每一次递归结束就可以得到一个递归数列
char * countAndSay(int n)
{
//申请堆空间,并初始化
char *result = (char *)malloc(sizeof(char) * MAX_STR_LEN);
memset(result, 0, sizeof(char) * MAX_STR_LEN);
//如果递归到n==1时,就可以返回了
if (n == 1) {
result[0] = '1';
return result;
}
//得到n-1的外观数列
char *tmp = countAndSay(n - 1);
int len = strlen(tmp);//计算外观数列的长度
//构造n的改观数列
int continueCnt = 0; // 连续数字的个数
int cnt = 0;
for (int i = 0; i < len; i++) {
continueCnt++;
int next = i + 1;
//如果已经到外观数列末尾或者本轮循环的元素与下一轮元素不相同,就表示tno[i]这个元素的个数统计结束
if (next >= len || tmp[next] != tmp[i]) {
//构造新的外观上数列
result[cnt++] = '0' + continueCnt;//赋值个数
result[cnt++] = tmp[i];//赋值本轮元素
//个数清零
continueCnt = 0;
}
}
//释放堆空间
free(tmp);
//结尾赋值'\0'表示结束符,否则返回到上一层时,用strlen会出错。开辟的堆空间时不会自动赋值'\0'
result[cnt] = '\0';
//返回结果
return result;
}
5.总结
对c语言的堆空间开辟函数要熟悉。其他的没有什么难度。