题目:38. 外观数列(Count and Say)
题目描述:
外观数列是一个整数序列,从数字 1 开始。后续每一项都是对前一项的描述(报数)。
n = 1时,输出"1"n = 2时,描述前一项"1"(1个1),输出"11"n = 3时,描述前一项"11"(2个1),输出"21"n = 4时,描述前一项"21"(1个2,1个1),输出"1211"
算法原理
- 外层循环(迭代生成) :从
i = 1开始遍历到n-1,每次根据当前的字符串ret生成下一项。 - 内层双指针(统计连续字符) :
- 使用
left和right两个指针。 right不断向右移动,直到遇到与left不同的字符或到达字符串末尾。- 此时
right - left就是连续相同字符的个数。 - 将个数 和字符 依次追加到临时容器中,然后让
left = right,继续下一组统计。
- 使用
- 类型转换 :利用
StringBuilder高效拼接字符串,生成新项后转为String赋值给ret。
解题模拟(以 n = 4 为例)
| 步骤 | 当前 ret 值 | 双指针统计过程 | 生成的 tmp 值 |
|---|---|---|---|
| 初始 | "1" |
- | - |
| i=1 | "1" |
统计到 1 个 '1' | "11" |
| i=2 | "11" |
统计到 2 个 '1' | "21" |
| i=3 | "21" |
统计到 1 个 '2',1 个 '1' | "1211" |
| 结束 | "1211" |
返回结果 | - |
代码答案
class Solution {
public String countAndSay(int n) {
String ret = "1";
for (int i = 1; i < n; i++) {
StringBuilder tmp = new StringBuilder();
int len = ret.length();
for (int left = 0, right = 0; right < len; ) {
// 重点:right < len 必须放在前面防止越界
while (right < len && ret.charAt(left) == ret.charAt(right)) {
right++;
}
tmp.append(Integer.toString(right - left)); // 追加数量
tmp.append(ret.charAt(left)); // 追加字符
left = right;
}
ret = tmp.toString(); // StringBuilder 转为 String
}
return ret;
}
}
知识点总结:String、StringBuilder 与数组对比
| 特性维度 | 数组 (Array) | String (字符串) | StringBuilder (可变字符串) |
|---|---|---|---|
| 可变性 | 可变(修改元素原地生效) | 不可变(修改会生成新对象) | 可变(原地修改,性能好) |
| 取值方式 | arr[index] |
charAt(index) |
无直接下标取值法 |
| 长度获取 | arr.length (属性) |
str.length() (方法) |
sb.length() (方法) |
| 追加/修改 | arr[index] = val |
不能用 + 频繁拼接 |
append(数据) 追加到末尾 |
| 类型转换 | - | Integer.toString(数字) |
sb.toString() 转为 String |
| 适用场景 | 固定长度数据存储 | 最终稳定结果、只读场景 | 循环内频繁拼接/修改字符串 |
核心避坑指南:
while循环判断边界时,right < len必须写在前面 (利用 Java 的短路与&&特性),否则会触发StringIndexOutOfBoundsException。String和StringBuilder类型不兼容,赋值时必须用toString()显式转换。