题目描述
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string]
,表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a
或 2[4]
的输入。
输入输出样例
s = "3[a]2[bc]", 返回 "aaabcbc".
s = "3[a2[c]]", 返回 "accaccacc".
s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef".
辅助栈
需要以下几个变量:
- 乘数栈:存储3、11、2这样的乘数
- 被乘字符串栈:存储每一阶段需要被乘的字符串
- 乘数:暂存乘数
- 当前字串:
循环遍历经过编码的字符串
- 如果碰到
[
,就将乘数入栈,同时将当前字串如栈
入栈以后,将原来的变量或置0,或清空(重赋值) - 如果碰到
]
,将栈中乘数弹出,用新变量存储
将当前字串乘以操作数,再将栈顶弹出,和它连接 - 如果碰到数字字符,可能会是连续的,如
12
,所以。。。 - 如果碰到的是其它字符,一律让此字符与
当前字串
拼接
代码:
class Solution {
public String decodeString(String s) {
// 存储乘数的栈
Stack<Integer> stack_multi = new Stack<Integer>();
// 存储被乘字符串的字符串
Stack<String> stack_str = new Stack<String>();
// 乘数
int multi = 0;
// 当前子串
StringBuilder ret = new StringBuilder();
// 开始遍历字符串
for(Character c : s.toCharArray()){
if(c == '['){
// 将乘数入栈,并置0
stack_multi.push(multi);
multi = 0;
// 将当前字串入栈,并重赋值
stack_str.push(ret.toString());
ret = new StringBuilder();
}
else if(c == ']'){
// 乘数弹出,用新变量存储
int cur_multi = stack_multi.pop();
// 当前字串乘以乘数
StringBuilder temp = new StringBuilder();
for(int i=0; i<cur_multi; i++) temp.append(ret);
// 将栈顶弹出,并和拼接好的字串连接
ret = new StringBuilder(stack_str.pop()+temp);
}
else if(c >= '0' && c <= '9') multi = multi*10 + Integer.parseInt(c+"");
else
ret.append(c);
}
return ret.toString();
}
}
递归法——使用全局变量
思路:
[
是开启递归的条件,此时需要存储 此刻的multi和ret
]
是结束递归的条件,此时直接返回即可。
以2[a3[bc]] 为例:
---------------------------
当前遍历 multi(乘数) ret(当前字串)
'2' 0 ""
'[' ----------开启递归---------- 存储本地值:cur_multi(2)、cur_str("")
置空 0 ""
'a' 0 "a"
'3' 3 "a"
'[' ----------开启递归---------- 存储本地值:cur_multi(3)、cur_str("a")
'b' 0 "b"
'c' 0 "bc"
']' ----------结束递归---------- 使用本地值:cur_multi(3)、cur_str("a")
使用本地值 0 "abcbcbc"
']' ----------结束递归---------- 使用本地值:cur_multi(2)、cur_str("")
使用本地值 0 "abcbcbcabcbcbc"
递归的时候,我使用了全局变量,这个决定让我的编码变得简单了,可以直接将上面辅助栈的代码拿来用。
但是运行速度上还是有点不足的
代码:
class Solution {
// 乘数
int multi = 0;
// 当前子串
StringBuilder ret = new StringBuilder();
// 索引
int index = 0;
public String decodeString(String s) {
while(index < s.length()){
char c = s.charAt(index++);
if(c == '['){
// 将乘数入栈,并置0
int cur_multi = multi;
multi = 0;
// 将当前字串入栈,并重赋值
String cur_str = ret.toString();
ret = new StringBuilder();
// 开启递归
decodeString(s);
// 递归返回之后
// 乘数弹出,用新变量存储
// ...
// 当前字串乘以乘数
StringBuilder temp = new StringBuilder();
for(int i=0; i<cur_multi; i++) temp.append(ret);
// 将栈顶弹出,并和拼接好的字串连接
ret = new StringBuilder(cur_str+temp);
}
else if(c == ']'){
return null;
}
else if(c >= '0' && c <= '9') multi = multi*10 + Integer.parseInt(c+"");
else
ret.append(c);
}
return ret.toString();
}
}
代码基本完全就是复制辅助栈法的
递归法——使用局部变量
思路基本一致,只是使用局部变量的时候,需要往回传参,但是不再需要保存递归前的全局变量了。
class Solution {/(O(n),O(n))
//当 s[i] == ']' 时,返回当前括号内记录的 res 字符串与 ] 的索引 i (更新上层递归指针位置);
//当 s[i] == '[' 时,开启新一层递归,记录此 [...] 内字符串 tmp 和递归后的最新索引 i,并执行 res + multi * tmp 拼接字符串。
//遍历完毕后返回 res。
public String decodeString(String s) {
return dfs(s, 0)[1];
}
private String[] dfs(String s, int i) {//字符串s和索引i,返回字符数组,索引i和结果string
StringBuilder res = new StringBuilder();
int multi = 0;//重复次数
while(i<s.length()){//没有走到结尾时
if(s.charAt(i)>='0'&&s.charAt(i)<='9'){//是数字
multi = multi*10+Integer.parseInt(String.valueOf(s.charAt(i)));
//计算数字的值
}else if(s.charAt(i)=='['){//递归开启
String[] tmp = dfs(s,i+1);
i = Integer.parseInt(tmp[0]);//遇到']',i=结尾索引,即右括号位置
while(multi>0){
res.append(tmp[1]);
multi--;
}
}else if(s.charAt(i)==']'){
return new String[] {String.valueOf(i),res.toString()};
}else{
res.append(s.charAt(i));
}
i++;
}
return new String[] {String.valueOf(i),res.toString()};
}
}
总结
- 能用迭代解决的问题,大多数时候递归都能够搞定。反之,也是一样的。
- 使用递归的时候,应该避免使用全局变量。全局变量容易让程序变得松散。