一、题目描述
给定一个正整数 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
二、解题思路
- 外观数列的生成规则是基于对前一项的描述,其中描述是基于连续相同数字的个数和该数字本身。
- 我们需要编写一个函数
countAndSay
来输出外观数列的第 n 项。 - 由于这是一个递归问题,我们可以从基本情况开始,即
countAndSay(1) = "1"
。 - 对于
n > 1
的情况,我们需要根据前一项来生成当前项。这涉及到遍历字符串,找到连续相同字符的序列,并将其转换为描述形式(例如,两个连续的 '1' 变成 "21")。 - 我们可以通过一个循环来实现这个递归过程,直到达到所需的项数。
三、具体代码
public class Solution {
public String countAndSay(int n) {
if (n == 1) return "1";
String prev = "1";
for (int i = 2; i <= n; i++) {
prev = nextTerm(prev);
}
return prev;
}
private String nextTerm(String term) {
StringBuilder sb = new StringBuilder();
int count = 1;
for (int i = 1; i < term.length(); i++) {
if (term.charAt(i) == term.charAt(i - 1)) {
count++;
} else {
sb.append(count).append(term.charAt(i - 1));
count = 1; // Reset count for the new character
}
}
// Append the last character's count and itself
sb.append(count).append(term.charAt(term.length() - 1));
return sb.toString();
}
}
四、时间复杂度和空间复杂度
1. 时间复杂度
- 该函数包含一个循环,该循环的次数是
n
,即我们需要生成的项数。 - 在每次循环中,我们调用
nextTerm
函数,这个函数的时间复杂度是O(k)
,其中k
是当前项的长度。 - 由于每一项的长度大约是前一项长度的两倍(这是因为每两个相同的数字会被合并为一个数字加一个计数),所以第
n
项的长度大约是2^(n-1)
。 - 因此,对于
nextTerm
函数,总的时间复杂度大约是O(2^(n-1) * (n-1))
,这是通过将每一项的长度累加得到的。 - 所以,
countAndSay
函数的总时间复杂度大约是O(n * 2^(n-1))
。
2. 空间复杂度
nextTerm
函数使用了StringBuilder
来构建结果字符串,其空间大小取决于输入字符串的长度,即O(k)
。- 由于
countAndSay
函数中没有使用额外的数据结构来存储除了输入和输出之外的任何信息,所以空间复杂度主要取决于StringBuilder
的使用。 - 因此,总的空间复杂度也是
O(k)
,其中k
是第n
项的长度。
五、总结知识点
-
递归思想:虽然代码实现中使用了迭代,但问题本身可以通过递归来解决。递归是一种编程范式,允许函数调用自身来解决问题。
-
字符串操作:代码中使用了
StringBuilder
来构建新的字符串,这是因为StringBuilder
在字符串拼接操作中比String
类更高效,尤其是在频繁修改字符串内容时。 -
循环结构:代码使用了一个
for
循环来迭代生成外观数列的每一项。循环结构是编程中实现重复任务的基本方法。 -
字符比较:在
nextTerm
方法中,通过比较当前字符和前一个字符来判断是否为连续相同的字符。 -
条件判断:使用
if
语句来决定何时追加计数和字符到StringBuilder
。 -
字符串拼接:在
StringBuilder
中使用append
方法来拼接数字和字符,最终生成新的字符串。 -
私有辅助方法:
nextTerm
是一个私有方法,用于生成外观数列的下一项。这是封装的一个例子,将一个复杂的功能分解成更小、更易于管理的部分。 -
返回值:函数通过返回字符串来输出结果,这是函数式编程中常见的一种模式。
-
边界条件处理:在函数开始时,检查
n
是否为 1,这是处理基本情况的一种常见做法,确保递归或迭代有一个明确的起始点。 -
变量命名:代码中的变量命名清晰,如
prev
表示前一项,count
表示当前字符的连续计数,这有助于代码的可读性和维护性。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。