38. 外观数列

175 阅读4分钟

【题目】

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

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

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

  • countAndSay(1) = "1"
  • countAndSay(n) 是对 countAndSay(n-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"

要 描述 一个数字字符串,首先要将字符串分割为 最小 数量的组,每个组都由连续的最多 相同字符 组成。然后对于每个组,先描述字符的数量,然后描述字符,形成一个描述组。要将描述转换为数字字符串,先将每组中的字符数量用数字替换,再将所有描述组连接起来。

例如,数字字符串 "3322251" 的描述如下图:

示例 1:

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

示例 2:

输入: 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

【题目解析】

思路

“外观数列”的生成可以视为一个迭代过程,每一次迭代都是对上一项的描述。为了生成序列的第n项,我们需要从第一项开始,逐项描述,直到达到第n项。

  1. 基础情况:如果n为1,那么结果即为字符串"1"
  2. 迭代构建:从第二项开始,观察上一项的数字,描述连续数字的个数和值,然后将这些描述拼接成新的字符串。
class Solution:
    def countAndSay(self, n: int) -> str:
        # 第一项直接返回
        if n == 1:
            return "1"
        
        # 第二项开始迭代构建序列
        prev_sequence = "1"
        for _ in range(n - 1):
            sequence = []
            i = 0
            while i < len(prev_sequence):
                count = 1
                # 计算相同数字连续出现的次数
                while i + 1 < len(prev_sequence) and prev_sequence[i] == prev_sequence[i + 1]:
                    i += 1
                    count += 1
                sequence.append(str(count) + prev_sequence[i])
                i += 1
            prev_sequence = ''.join(sequence)
        
        return prev_sequence

执行

截屏2024-02-09 11.05.20.png

【总结】

外观数列问题是一个典型的字符串迭代构造问题,它不仅考验对字符串的操作能力,也是递归思想与迭代方法应用的绝佳示例。

适用问题范围

此类迭代方法适用于一系列的序列生成问题,特别是那些可以通过前一项推导出后一项的场景。这包括但不限于:

  • 序列预测问题
  • 基于规则的字符串生成问题
  • 某些类型的编码和解码问题

使用的算法

在解决外观数列问题时,我们采用的主要算法是迭代法,它适合处理此类问题,因为每一项的生成只依赖于前一项。这种方法相比递归有几个优势:

  • 空间效率:迭代法不需要额外的调用栈,节省了空间。
  • 时间效率:迭代法通常会比递归法更快,因为它省去了函数调用的开销。
  • 易于理解:迭代法通常更直观,更容易跟踪和调试。

解题策略

在具体实现上,我们采取了几个关键策略:

  1. 逐字符扫描:我们逐一扫描上一项的字符,统计连续相同字符的个数,并据此构造新的字符串。
  2. 列表构建:使用列表来累积新字符串的各个部分,这比不断修改不可变的字符串对象更高效。
  3. 字符串合并:在迭代的每一步结束时,使用join方法将列表中的字符串合并,生成新的项。

总结

外观数列问题的解法展示了迭代算法在处理自相似结构数据时的优势,同时也说明了在算法设计中,适当的数据结构选择对性能的影响。虽然递归方法可以直观地表达问题的本质,迭代方法在实际应用中往往能提供更优的性能和可控性。此外,优化字符串操作对于处理文本数据的问题来说至关重要,特别是在数据量大的情况下,优化方法能够显著提升效率。

这类问题的解决方案不仅提升了我们对字符串处理的认识,也加深了我们对递归和迭代之间差异的理解。在日常的软件开发和算法设计中,这些技能和知识是我们宝贵的工具,它们可以帮助我们更高效地解决问题,并创造出更加优雅的代码。

题目链接

外观数列