力扣题目-外观数列

81 阅读6分钟

一、题目

「外观数列」是一个数位字符串序列,由递归公式定义:

  • countAndSay(1) = "1"
  • countAndSay(n) 是 countAndSay(n-1) 的行程长度编码。

 

行程长度编码(RLE)是一种字符串压缩方法,其工作原理是通过将连续相同字符(重复两次或更多次)替换为字符重复次数(运行长度)和字符的串联。例如,要压缩字符串 "3322251" ,我们将 "33" 用 "23" 替换,将 "222" 用 "32" 替换,将 "5" 用 "15" 替换并将 "1" 用 "11" 替换。因此压缩后字符串变为 "23321511"

给定一个整数 n ,返回 外观数列 的第 n 个元素。

示例 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

二、解答

题目分析

  1. 核心概念:本题的关键在于理解 “外观数列” 的定义,它是通过递归的方式,对前一个数列进行行程长度编码得到下一个数列。行程长度编码的操作是将连续相同的字符,用字符出现的次数和该字符本身串联来替换。

  2. 输入输出:输入是一个整数 n,表示要获取外观数列的第 n 个元素;输出是一个字符串,即外观数列的第 n 个元素对应的行程长度编码字符串。

  3. 约束条件:题目限定了 1 <= n <= 30,这意味着我们在计算外观数列的过程中,最多只需要进行 30 次递归计算行程长度编码,不需要考虑更大规模的计算情况。

  4. 解题思路

    • 可以通过循环或递归的方式来求解。从基本情况 countAndSay(1) = "1" 开始,根据行程长度编码的规则,依次计算出 countAndSay(2)countAndSay(3) 直到 countAndSay(n)
    • 在实现行程长度编码时,需要遍历字符串,统计连续相同字符的个数,并按照规则生成新的字符串。

示例分析

  1. 示例 1

    • 输入n = 4

    • 计算过程

      • 首先,countAndSay(1) = "1",这是外观数列的起始值。
      • 计算 countAndSay(2) 时,对 countAndSay(1) 的值 "1" 进行行程长度编码。因为 "1" 中只有 1 个 1,所以编码后得到 "11"
      • 计算 countAndSay(3) 时,对 countAndSay(2) 的值 "11" 进行行程长度编码。"11" 中有 2 个 1,所以编码后得到 "21"
      • 计算 countAndSay(4) 时,对 countAndSay(3) 的值 "21" 进行行程长度编码。"21" 中是 1 个 2 和 1 个 1,所以编码后得到 "1211"
    • 输出"1211"

  2. 示例 2

    • 输入n = 1
    • 计算过程:根据外观数列的定义,countAndSay(1) = "1",这是基本情况,不需要对其他字符串进行行程长度编码。
    • 输出"1"

代码

Java
class Solution {
    public String countAndSay(int n) {
        String str = "1";
        for (int i = 2; i <= n; ++i) {
            StringBuilder sb = new StringBuilder();
            int start = 0;
            int pos = 0;

            while (pos < str.length()) {
                while (pos < str.length() && str.charAt(pos) == str.charAt(start)) {
                    pos++;
                }
                sb.append(Integer.toString(pos - start)).append(str.charAt(start));
                start = pos;
            }
            str = sb.toString();
        }
        
        return str;
    }
}

python
class Solution:
    def countAndSay(self, n: int) -> str:
        prev = "1"
        for i in range(n-1):
            curr = ""
            pos = 0
            start = 0

            while pos < len(prev):
                while pos < len(prev) and prev[pos] == prev[start]:
                    pos += 1
                curr += str(pos - start) + prev[start]
                start = pos
            prev = curr
        
        return prev

代码分析

Java
1. 类和方法定义

java

class Solution {
    public String countAndSay(int n) {
  • class Solution:定义了一个名为 Solution 的类。
  • public String countAndSay(int n):定义了一个公共方法 countAndSay,它接受一个整数参数 n,并返回一个字符串。
2. 初始化

java

String str = "1";
  • 初始化变量 str 为 "1",这是外观数列的第一项。
3. 循环生成外观数列

java

for (int i = 2; i <= n; ++i) {
  • 使用 for 循环从第 2 项开始,直到第 n 项。
4. 构建当前项

java

StringBuilder sb = new StringBuilder();
int start = 0;
int pos = 0;
  • StringBuilder sb:用于构建当前项的字符串。
  • start:记录当前连续相同数字的起始位置。
  • pos:用于遍历字符串。
5. 遍历前一项字符串

java

while (pos < str.length()) {
    while (pos < str.length() && str.charAt(pos) == str.charAt(start)) {
        pos++;
    }
    sb.append(Integer.toString(pos - start)).append(str.charAt(start));
    start = pos;
}
  • 外层 while 循环确保遍历完前一项字符串。
  • 内层 while 循环找到连续相同数字的结束位置。
  • sb.append(Integer.toString(pos - start)).append(str.charAt(start)):将连续相同数字的个数和该数字添加到 StringBuilder 中。
  • start = pos:更新起始位置。
6. 更新当前项

java

str = sb.toString();

  • 将 StringBuilder 中的内容转换为字符串,并更新 str 为当前项。
7. 返回结果

java

return str;

  • 返回第 n 个外观数列。
python
1. 类和方法定义

python

class Solution:
    def countAndSay(self, n: int) -> str:
  • class Solution:定义了一个名为 Solution 的类。
  • def countAndSay(self, n: int) -> str:定义了类中的 countAndSay 方法,它接收一个整数参数 n,并返回一个字符串类型的结果。
2. 初始化

python

prev = "1"
  • 把变量 prev 初始化为 "1",这代表外观数列的首项。
3. 循环生成外观数列

python

for i in range(n - 1):
  • 利用 for 循环从第 2 项开始生成,直至第 n 项。因为已经将首项初始化为 "1",所以只需循环 n - 1 次。
4. 构建当前项

python

curr = ""
pos = 0
start = 0
  • curr:用于构建当前要生成的外观数列项。
  • pos:在遍历前一项字符串时,作为当前位置的索引。
  • start:标记当前连续相同数字的起始位置。
5. 遍历前一项字符串

python

while pos < len(prev):
    while pos < len(prev) and prev[pos] == prev[start]:
        pos += 1
    curr += str(pos - start) + prev[start]
    start = pos
  • 外层 while 循环保证遍历完前一项字符串。
  • 内层 while 循环找出连续相同数字的结束位置。
  • curr += str(pos - start) + prev[start]:把连续相同数字的数量和该数字组合成字符串添加到 curr 中。
  • start = pos:更新起始位置,以便继续处理后续数字。
6. 更新当前项

python

prev = curr

  • 将 curr 赋值给 prev,使 prev 成为下一次循环中的前一项。
7. 返回结果

python

return prev

  • 返回第 n 个外观数列。

总结

两段代码分别使用 Java 和 Python 实现了相同的功能,即生成第 n 个「外观数列」。「外观数列」是一个从 "1" 开始的整数序列,后续每一项都是对前一项数字分布的描述。

  • Java 代码:定义了 Solution 类,在 countAndSay 方法中,使用 StringBuilder 来构建新的字符串,利用 while 循环进行字符串遍历和处理。
  • Python 代码:同样定义了 Solution 类,在 countAndSay 方法中,使用普通字符串拼接来构建新字符串,借助 while 循环完成字符串操作。