关注
文末的名片达文汐
,回复关键词“力扣源码”,即可获取完整源码!!详见:源码和核心代码的区别
题目详情
「外观数列」是一个数位字符串序列,由递归公式定义:
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
解题思路
- 问题分析:外观数列的第
n
项依赖于第n-1
项,可以通过迭代逐步生成。需要高效处理字符串的行程编码。 - 核心优化:
- 双指针高效统计:使用快慢指针统计连续字符,减少循环次数
- 预分配空间:根据前一项长度估算新字符串容量(2倍),减少扩容开销
- 原地构建:直接操作字符数组,避免
charAt()
的边界检查开销
- 关键操作:
- 初始化当前字符串为
"1"
- 对于每个迭代步骤:
- 将字符串转为字符数组,避免多次调用
charAt()
- 快指针扫描连续相同字符,慢指针标记起始位置
- 直接计算连续字符长度(快慢指针差值)
- 将长度和字符拼接到
StringBuilder
- 将字符串转为字符数组,避免多次调用
- 更新当前字符串为下一项
- 初始化当前字符串为
- 边界处理:当
n=1
时直接返回"1"
代码实现(Java版)
class Solution {
public String countAndSay(int n) {
// 基本情况处理
if (n == 1) return "1";
// 初始化为第一项
String current = "1";
// 迭代生成第2到第n项
for (int i = 2; i <= n; i++) {
// 预分配空间(新字符串长度不超过前一项2倍)
StringBuilder sb = new StringBuilder(current.length() * 2);
// 转为字符数组提高访问效率
char[] chars = current.toCharArray();
int slow = 0; // 慢指针:连续字符起始位置
int fast = 0; // 快指针:扫描连续字符
// 双指针遍历字符数组
while (fast < chars.length) {
// 移动快指针直到遇到不同字符
while (fast < chars.length && chars[fast] == chars[slow]) {
fast++;
}
// 计算连续字符长度
int count = fast - slow;
// 拼接出现次数和字符
sb.append(count).append(chars[slow]);
// 移动慢指针到新的连续起始位置
slow = fast;
}
// 更新当前字符串
current = sb.toString();
}
return current;
}
}
代码说明
- 初始化处理:当
n=1
时直接返回初始值"1"
- 迭代优化:
- 使用字符数组
chars
存储当前字符串,减少charAt()
调用开销 - 双指针(
slow
和fast
)高效统计连续字符:slow
标记连续字符起始位置fast
扫描至连续字符结束位置- 连续字符长度 =
fast - slow
- 使用字符数组
- 高效拼接:
- 预分配
StringBuilder
容量为前一项长度的2倍 - 直接使用
append()
拼接整数和字符
- 预分配
- 复杂度分析:
- 时间复杂度:O(L) 每轮迭代,L 为当前字符串长度
- 空间复杂度:O(L) 存储当前字符串和字符数组