给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
测试用例保证输出的长度不会超过 105。
示例 1:
输入: s = "3[a]2[bc]"
输出: "aaabcbc"
示例 2:
输入: s = "3[a2[c]]"
输出: "accaccacc"
示例 3:
输入: s = "2[abc]3[cd]ef"
输出: "abcabccdcdcdef"
示例 4:
输入: s = "abc3[cd]xyz"
输出: "abccdcdcdxyz"
提示:
1 <= s.length <= 30s由小写英文字母、数字和方括号'[]'组成s保证是一个 有效 的输入。s中所有整数的取值范围为[1, 300]
1. 套娃快递拆箱
假设你收到一个奇怪的快递,里面有很多层层嵌套的盒子:
- 规则: 看到数字(比如
3),表示接下来的盒子要重复 3 次。看到[,表示开始进入一个新盒子。看到],表示当前盒子处理完了,要把里面的东西拿出来,按次数拼好,交给上一层盒子。
你会怎么记? 因为盒子可能嵌套(大盒套小盒),你得拿个记事本,把还没处理完的“任务”先记下来。
-
遇到数字: 先把数字存起来(比如
3)。 -
遇到
[(进新盒子):- 把当前已经拼好的字符串和当前的数字一股脑儿扔进“备忘录(栈)”里。
- 然后清空手里的活,开始记录这个新盒子里面的内容。
-
遇到字符: 直接记在手里。
-
遇到
](拆完一个盒子):- 从备忘录(栈)里弹出最近的一次任务。
- 把刚才手里拼好的内容重复
N次,贴在弹出来的“前缀字符串”后面。
2. 代码实现(JavaScript)
JavaScript
/**
* @param {string} s
* @return {string}
*/
var decodeString = function(s) {
let stack = []; // 备忘录:存放 [当前倍数, 之前的字符串]
let currStr = ""; // 当前正在处理的字符串
let num = 0; // 当前正在记录的倍数
for (let char of s) {
if (char >= '0' && char <= '9') {
// 如果是数字(注意处理多位数,比如 10[a])
num = num * 10 + Number(char);
} else if (char === '[') {
// 进盒子了!把手里的存货(之前的字符串和倍数)扔进备忘录
stack.push([currStr, num]);
// 清空手头工具,准备接纳新盒子里的内容
currStr = "";
num = 0;
} else if (char === ']') {
// 盒子拆完了!
let [prevStr, repeatCount] = stack.pop();
// 把现在的字符串重复 N 次,接在老字符串后面
currStr = prevStr + currStr.repeat(repeatCount);
} else {
// 普通字母,直接接在手里
currStr += char;
}
}
return currStr;
};
3. 逻辑总结
记住这四个“动作”:
- 数字: 攒倍数(
num = num * 10 + char)。 - 左括号
[: 既然要进入“子问题”了,就把“父问题”的现场(已有的字符串和倍数)存入栈中保护起来。 - 右括号
]: 一个子问题结束了。从栈里取出父问题的现场,把子问题的结果重复一下,合到父问题上。 - 字母: 搬砖,直接拼在当前字符串后面。
举例 3[a]2[bc] 的过程:
- 遇到
3,num = 3。 - 遇到
[,把["", 3]存入栈,currStr变回空。 - 遇到
a,currStr = "a"。 - 遇到
],取出["", 3],把"a"重复 3 次,得到currStr = "aaa"。 - 后面同理处理
2[bc],最后拼成"aaabcbc"。
这样处理,无论套娃有多少层(3[a2[c]]),栈都会帮你自动排好序!