题目

给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
测试用例保证输出的长度不会超过 105。
数据范围
1 <= s.length <= 30
s 由小写英文字母、数字和方括号 '[]' 组成
s 保证是一个 有效 的输入。
s 中所有整数的取值范围为 [1, 300]
测试用例
示例1
java
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
示例2
java
输入:s = "3[a2[c]]"
输出:"accaccacc"
示例3
java
输入:s = "2[abc]3[cd]ef"
输出:"abcabccdcdcdef"
示例4
java
输入:s = "abc3[cd]xyz"
输出:"abccdcdcdxyz"
题解1(博主解法,时空OS,双栈)
java
import java.util.Stack;
class Solution {
public String decodeString(String s) {
Stack<Integer> countStack = new Stack<>();
Stack<StringBuilder> resStack = new Stack<>();
StringBuilder currentString = new StringBuilder();
int k = 0;
for (char c : s.toCharArray()) {
if (Character.isDigit(c)) {
// 处理多位数字,例如 "100[a]"
k = k * 10 + (c - '0');
} else if (c == '[') {
// 遇到 [,把当前的重复次数和当前已经拼接的字符串推入栈中
countStack.push(k);
resStack.push(currentString);
// 重置当前状态,准备处理括号里面的内容
currentString = new StringBuilder();
k = 0;
} else if (c == ']') {
// 遇到 ],开始结算
StringBuilder decodedString = resStack.pop(); // 拿到 [ 之前的字符串
int currentK = countStack.pop(); // 拿到重复次数
// 把括号里的内容 (currentString) 重复 currentK 次,拼接到 decodedString 后面
for (int i = 0; i < currentK; i++) {
decodedString.append(currentString);
}
// 更新当前字符串为最新结果
currentString = decodedString;
} else {
// 普通字符,直接添加
currentString.append(c);
}
}
return currentString.toString();
}
}
题解2(官解1 时空同上,单栈,翻转)
java
import java.util.Collections;
import java.util.LinkedList;
class Solution {
int ptr; // 全局指针,用于遍历字符串 s
public String decodeString(String s) {
// 使用 LinkedList 作为栈 (Stack),这里存的是 String 类型
// 它可以存数字字符串 "3"、左括号 "["、或者字母 "a"
LinkedList<String> stk = new LinkedList<String>();
ptr = 0;
while (ptr < s.length()) {
char cur = s.charAt(ptr);
if (Character.isDigit(cur)) {
// 1. 如果是数字:解析出完整的数字(处理多位数)
String digits = getDigits(s);
stk.addLast(digits); // 入栈
}
else if (Character.isLetter(cur) || cur == '[') {
// 2. 如果是字母或者左括号:
// 直接转成字符串入栈,作为"存档"
stk.addLast(String.valueOf(s.charAt(ptr++)));
}
else {
// 3. 如果是右括号 ']':开始"结算"
++ptr; // 跳过 ']'
// 准备一个临时列表,用来收集括号里的字母
LinkedList<String> sub = new LinkedList<String>();
// 3.1 从栈顶一直往外弹,直到遇到左括号 "["
while (!"[".equals(stk.peekLast())) {
sub.addLast(stk.removeLast());
}
// 3.2 因为栈是后进先出,取出来的顺序是反的(例如 c, b),所以要反转回 (b, c)
Collections.reverse(sub);
// 3.3 左括号 "[" 出栈(用完了,丢弃)
stk.removeLast();
// 3.4 此时栈顶一定是数字(重复次数),把它弹出来转成 int
int repTime = Integer.parseInt(stk.removeLast());
// 3.5 构造新的字符串
StringBuffer t = new StringBuffer();
String o = getString(sub); // 将 list 里的字符拼成字符串
// 根据倍数进行重复拼接
while (repTime-- > 0) {
t.append(o);
}
// 3.6 【关键一步】将算好的字符串(例如 "acc")重新压入栈中
// 这样如果外层还有括号(嵌套情况),它就变成了外层的一部分
stk.addLast(t.toString());
}
}
// 最后将栈里剩下的所有片段拼起来返回
return getString(stk);
}
// 辅助函数:解析数字
public String getDigits(String s) {
StringBuffer ret = new StringBuffer();
while (Character.isDigit(s.charAt(ptr))) {
ret.append(s.charAt(ptr++));
}
return ret.toString();
}
// 辅助函数:将 LinkedList 里的字符串拼起来
public String getString(LinkedList<String> v) {
StringBuffer ret = new StringBuffer();
for (String s : v) {
ret.append(s);
}
return ret.toString();
}
}
题解3(官解2,递归,时空O(S+|s|)(栈内存,堆内存))
java
class Solution {
String src; // 保存源字符串
int ptr; // 全局指针,记录当前处理到了哪里
public String decodeString(String s) {
src = s;
ptr = 0;
return getString(); // 启动递归
}
public String getString() {
// Base Case (递归终止条件):
// 1. 指针走到了字符串末尾
// 2. 或者遇到了 ']' (说明这一层括号里的内容处理完了,该返回了)
if (ptr == src.length() || src.charAt(ptr) == ']') {
return "";
}
char cur = src.charAt(ptr);
int repTime = 1;
String ret = "";
if (Character.isDigit(cur)) {
// 情况 1: 遇到数字,格式为 Digits [ String ]
// 1.1 解析数字 (例如 "3")
repTime = getDigits();
// 1.2 过滤掉左括号 '['
++ptr;
// 1.3 【核心递归】
// 遇到括号了,递归调用 getString() 去解析括号里面的内容
// 这一步会一直运行,直到遇到对应的 ']' 才会返回结果
String str = getString();
// 1.4 过滤掉右括号 ']' (递归返回后,指针正好指在 ']')
++ptr;
// 1.5 根据次数构建当前这部分的字符串
while (repTime-- > 0) {
ret += str; // 注意:这里用 String 拼接效率较低,建议用 StringBuilder
}
}
else if (Character.isLetter(cur)) {
// 情况 2: 遇到普通字母
// 直接获取该字母
ret = String.valueOf(src.charAt(ptr++));
}
// 【非常关键的一步】
// 递归处理后续的字符串。
// 因为当前的 ret 只是解析了 "3[a]" 或者 "b",后面可能还有 "2[c]"
// 所以要 return 当前部分 + 剩下的部分(递归结果)
return ret + getString();
}
// 辅助函数:解析数字
public int getDigits() {
int ret = 0;
while (ptr < src.length() && Character.isDigit(src.charAt(ptr))) {
ret = ret * 10 + src.charAt(ptr++) - '0';
}
return ret;
}
}
思路
这道题虽然是普通题,但个人觉得是普通题中偏难一点的题,因为这道题中存在括号的嵌套,所以单层模拟肯定不行,会无法处理嵌套,所以需要栈。用栈的话有两种方式,一种是官解的单栈,只有一个判定节点,也就是后括号,遇到后括号一个个弹出,直到遇到左括号。再往前弹一个就是数字。但频繁使用翻转,时间效率肯定不如双栈的。
双栈就是一个栈用来存储数字,一个栈用于存字符串。这种逻辑更清晰一点,速率也更快一些,可以根据自己的需求选择。
最后一个方法是递归,递归我还不太熟练,就引用Ai讲解
java
1. 核心思想:遇到困难,直接"甩锅"
当我们面对 3[a2[c]] 这个字符串时,我们的目标是把它完全解压。
递归的逻辑是这样的:
我是老板 (第0层):我看到了 3[。我知道我要把括号里的东西重复 3 次。
遇到困难:但我不知道括号里的 a2[c] 到底解压出来是什么。
甩锅 (递归调用):于是我喊来一个临时工 (第1层),对他说:"你帮我把 a2[c] 这一段解压了,把结果给我就行,其他的你别管。"
等待:我就停在这里,等临时工给我结果。
2. 临时工的视角 (第1层)
临时工拿到了 a2[c]:
他先读到了 a,记下来:"好,开头是 a"。
接着他看到了 2[。他也知道要把括号里的东西重复 2 次。
遇到困难:但他也不知道里面的 c 是个啥(虽然我们看着简单,但在程序眼里这又是一个新结构)。
继续甩锅 (递归调用):于是他也喊来一个小弟 (第2层),说:"你帮我把 c 解压了,结果给我。"
等待:临时工也停下来等。
3. 小弟的视角 (第2层 - 最底层)
小弟拿到了 c]:
他读到了 c,记下来。
接着他读到了 ] (右括号)。
任务结束 (Base Case):右括号意味着"这一层结束了"。
汇报:小弟对临时工说:"我干完了,结果是 "c"。"然后小弟就撤了(内存释放)。
4. 回溯:逐层交作业
回到临时工 (第1层):
小弟告诉他是 "c"。
临时工记得指令是 2[...]。
所以他把 "c" 重复 2 次,变成了 "cc"。
他之前还记了个 a,拼起来就是 "acc"。
此时他也遇到了他那一层的 ]。
汇报:他对老板说:"结果是 "acc"。"然后临时工也撤了。
回到老板 (第0层):
临时工告诉他是 "acc"。
老板记得指令是 3[...]。
老板把 "acc" 重复 3 次,变成 "accaccacc"。
任务全部完成。