38. 外观数列(C++)

38. 外观数列

给定一个正整数 n ,输出外观数列的第 n 项。

「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。

你可以将其视作是由递归公式定义的数字字符串序列:

countAndSay(1) = “1”
countAndSay(n) 是对 countAndSay(n-1) 的描述,然后转换成另一个数字字符串。

到这里有点不知所云,看下面例子就知道题目具体要求了。

示例1:

  1. 1
    
  2. 11
    
  3. 21
    
  4. 1211
    
  5. 111221
    第一项是数字 1 
    描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 "11"
    描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 "21"
    描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 "1211"
    描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 "111221"
    

示例2:

输入:n = 1
输出:“1”
解释:这是一个基本样例。

示例3:

输入:n = 4
输出:“1211”
解释:
countAndSay(1) = “1”
countAndSay(2) = 读 “1” = 一 个 1 = “11”
countAndSay(3) = 读 “11” = 二 个 1 = “21”
countAndSay(4) = 读 “21” = 一 个 2 + 一 个 1 = “12” + “11” = “1211”

提示:

  • 1 <= n <= 30

看到题目首先想到递归,但是功力不足,想不出来,于是转战顺序遍历生成

顺序遍历

class Solution {
public:
	string num = "1";   //当前的数字
	int times = 1;      //频率
	char* before_num = new char[2];  //申请两个字节
	string result;      //根据当前数字得到的描述字符串
	string countAndSay(int n) {
		before_num[1] = '\0';   //把第二个位置变为'\0',结束符号
		for (int i = 1; i<n; i++) {   //循环n-1次
			result = "";
			before_num[0] = num[0];
			for (int j = 1; j<num.size(); j++) {  //循环整个数值长度,除了第一个
				if (before_num[0] == num[j]) {   //如果相同则增加次数
					times++;
				}
				else {  //如果遇到不相同的  
					string times_str = std::to_string(times);     //转化为字符串
					result.append(times_str);
					result.append(before_num);
					before_num[0] = num[j];
					times = 1;
				}
			}
			string times_str = std::to_string(times);     //因为最后一个字符被跳掉了,再次执行一次
			result.append(times_str);
			result.append(before_num);
			num = result;
            times = 1;
		}

		return num;
	}
};

编写过程中,学到了一些东西,如:

new char(X)与new char[X]区别

  1. new char(X)是申请一个字节的空间,然后初始化为X。
  2. new char[X]是申请X个字节的空间,没有初始化。

例如下面代码

	//new char(X)
	char *str = new char(97);//a
	cout << str << endl;
	//new char[X]
	char *str2 = new char[2];
	str2[0] = 98;//b
	str2[1] = 99;//c

我们希望输出是

a
bc

但是实际输出是

在这里插入图片描述

后面部分乱码了,这是因为申请空间时,并没有把下一个字符变为结束符’\0’,使得编译器以为后面一连串的空间都是我们想要的,要解决这个问题,就多申请一个空间,然后把最后一个赋值为为’\0’

	char *str2 = new char[3];
	str2[0] = 98;//b
	str2[1] = 99;//c
	str2[2] = '\0';

std::to_string() 数字转化为string

用法:string a = std::to_string(5);

顺序遍历2

为什么上面要使用一个char*类型来存放一个字节的数据呢?

因为一开始不知道str.append(num,chr)就可以把num个字符变量chr追加到str后面。

或者直接使用重载的+即可!str = str+chr

官方的代码很简洁

class Solution {
public:
    string countAndSay(int n) {
        string prev = "1";
        for (int i = 2; i <= n; ++i) {
            string curr = "";
            int start = 0;
            int pos = 0;

            while (pos < prev.size()) {
                while (pos < prev.size() && prev[pos] == prev[start]) {
                    pos++;
                }
                curr += to_string(pos - start) + prev[start];
                start = pos;
            }
            prev = curr;
        }
        
        return prev;
    }
};

改变了思路,主要通过两个索引指针来进行定位,而出现的频次直接通过前后索引相减得到!

复杂度分析

  • 时 间 复 杂 度 : O ( N × M ) 时间复杂度:O(N \times M) O(N×M) 其中 N 为给定的正整数,M 为生成的字符串中的最大长度。
  • 空 间 复 杂 度 : O ( M ) 。 空间复杂度:O(M)。 O(M) 其中 M 为生成的字符串中的最大长度。

递归求解

参考大佬的解法,下面是原文关于递归的描述,醍醐灌顶

使用递归求解,一定不要用大脑去模拟递归的过程。大脑能压几个栈?

正确的做法是:记住递归函数的定义。比如本题中的递归函数 countAndSay(int n)含义是当取值为 n 时的外观数列。

那么,必须先求出取值为n−1 时的外观数列,怎么求?根据递归函数的定义,就是 countAndSay(n - 1)。至于 countAndSay(n - 1) 怎么算的,我们不用管。只要知道这个函数能给我们正确的结果就行。

class Solution {
public:
    string countAndSay(int n) {
        if (n == 1) {	// 递归的出口
            return "1";
        }
        string before = countAndSay(n - 1);
        string res;
        char cur = before[0];
        int count = 1;
        for (int i = 1; i < before.size(); ++i) {
            if (before[i] != cur) {
                res += to_string(count) + cur;
                cur = before[i];
                count = 0;
            }
            count ++;
        }
        res += to_string(count) + cur;
        return res;
    }
};

计数的做法就是一开始自己编写的顺序遍历,利用一个变量来存储出现的次数,而且到最后把最后一个字符算进去。

复杂度分析

与上面相同。递归的时间复杂度和遍历是一样的,因为 1…n 中的每个数字都被计算了一次。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值