题目
标题和出处
标题:字符串解码
出处:394. 字符串解码
难度
6 级
题目描述
要求
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string] \texttt{k[encoded\_string]} k[encoded_string],表示其中方括号内部的 encoded_string \texttt{encoded\_string} encoded_string 正好重复 k \texttt{k} k 次。注意 k \texttt{k} k 保证为正整数。
你可以认为输入字符串总是有效的。输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k \texttt{k} k,例如不会出现像 3a \texttt{3a} 3a 或 2[4] \texttt{2[4]} 2[4] 的输入。
示例
示例 1:
输入:
s
=
"3[a]2[bc]"
\texttt{s = "3[a]2[bc]"}
s = "3[a]2[bc]"
输出:
"aaabcbc"
\texttt{"aaabcbc"}
"aaabcbc"
示例 2:
输入:
s
=
"3[a2[c]]"
\texttt{s = "3[a2[c]]"}
s = "3[a2[c]]"
输出:
"accaccacc"
\texttt{"accaccacc"}
"accaccacc"
示例 3:
输入:
s
=
"2[abc]3[cd]ef"
\texttt{s = "2[abc]3[cd]ef"}
s = "2[abc]3[cd]ef"
输出:
"abcabccdcdcdef"
\texttt{"abcabccdcdcdef"}
"abcabccdcdcdef"
示例 4:
输入:
s
=
"abc3[cd]xyz"
\texttt{s = "abc3[cd]xyz"}
s = "abc3[cd]xyz"
输出:
"abccdcdcdxyz"
\texttt{"abccdcdcdxyz"}
"abccdcdcdxyz"
数据范围
- 1 ≤ s.length ≤ 30 \texttt{1} \le \texttt{s.length} \le \texttt{30} 1≤s.length≤30
- s \texttt{s} s 只包含小写英语字母、数字和方括号 ‘[]’ \texttt{`[]'} ‘[]’
- s \texttt{s} s 保证是有效的输入
- s \texttt{s} s 中的所有整数的取值范围是 [1, 300] \texttt{[1, 300]} [1, 300]
解法一
思路和算法
编码字符串使用方括号表示需要重复的片段,方括号可能出现嵌套的情况,如果出现嵌套的情况,则按照从内层到外层的顺序解码。由于解码时需要根据每一对匹配的方括号进行解码,寻找匹配的方括号需要借助栈的数据结构,因此可以使用栈实现解码。
由于栈内存储的元素为字符,包括字母和方括号,因此可以使用 StringBuffer \texttt{StringBuffer} StringBuffer 类型的变量代替栈,以下称为「字符串栈」。除了字母和方括号以外,编码字符串中还包含数字,为了区分数字和非数字,因此另外使用一个栈存储数字,以下称为「数字栈」。从左到右遍历字符串 s s s,对于每种类型的字符,操作分别如下:
-
当前字符是数字,则使用当前字符更新重复次数;
-
当前字符是字母,则将当前字符入字符串栈;
-
如果字符是左方括号,则一个数字遍历结束,将重复次数入数字栈,并将当前字符入字符串栈;
-
如果字符是右方括号,则一对方括号遍历结束,根据当前的一对方括号和方括号前的数字解码,解码操作如下:
-
将字符串栈的字母依次出栈,直到遇到左方括号,用 temp \textit{temp} temp 存储出栈的字母,然后将左方括号从字符串栈出栈;
-
将 temp \textit{temp} temp 反转,反转后的 temp \textit{temp} temp 中的字母顺序与进入字符串栈的顺序相同;
-
将数字栈的栈顶元素 k k k 出栈,则 temp \textit{temp} temp 需要在解码字符串中重复 k k k 次,因此执行 k k k 次将 temp \textit{temp} temp 入字符串栈的操作。
-
遍历结束之后,字符串栈按照从栈底到栈顶的顺序即为解码字符串。
考虑如下例子: s = “a3[b2[cd]]ef" s = \text{``a3[b2[cd]]ef"} s=“a3[b2[cd]]ef",编码字符串的长度为 12 12 12。解码操作如下。
下标 0 0 0 处的字符是字母,将字母入字符串栈。字符串栈为 “a" \text{``a"} “a",数字栈为 [ ] [] [],其中字符串栈用字符串代替栈,字符串栈和数字栈都是左边为栈底,右边为栈顶。
下标 1 1 1 处的字符是数字,重复次数更新为 3 3 3。
下标 2 2 2 处的字符是左方括号,将重复次数入数字栈,并将左方括号入字符串栈。字符串栈为 “a[" \text{``a["} “a[",数字栈为 [ 3 ] [3] [3]。
下标 3 3 3 处的字符是字母,将字母入字符串栈。字符串栈为 “a[b" \text{``a[b"} “a[b",数字栈为 [ 3 ] [3] [3]。
下标 4 4 4 处的字符是数字,重复次数更新为 2 2 2。
下标 5 5 5 处的字符是左方括号,将重复次数入数字栈,并将左方括号入字符串栈。字符串栈为 “a[b[" \text{``a[b["} “a[b[",数字栈为 [ 3 , 2 ] [3, 2] [3,2]。
下标 6 6 6 和 7 7 7 处的字符都是字母,将字母入字符串栈。字符串栈为 “a[b[cd" \text{``a[b[cd"} “a[b[cd",数字栈为 [ 3 , 2 ] [3, 2] [3,2]。
下标 8 8 8 处的字符是右方括号,执行如下操作。
-
将字符串栈的字母依次出栈直到遇到左方括号,然后将左方括号从字符串栈出栈, temp = “dc" \textit{temp} = \text{``dc"} temp=“dc"。
-
将出栈的字母反转, temp = “cd" \textit{temp} = \text{``cd"} temp=“cd"。
-
将数字栈的栈顶元素 k = 2 k = 2 k=2 出栈,执行 2 2 2 次将 “cd" \text{``cd"} “cd" 入字符串栈。字符串栈为 “a[bcdcd" \text{``a[bcdcd"} “a[bcdcd",数字栈为 [ 3 ] [3] [3]。
下标 9 9 9 处的字符是右方括号,执行如下操作。
-
将字符串栈的字母依次出栈直到遇到左方括号,然后将左方括号从字符串栈出栈, temp = “dcdcb" \textit{temp} = \text{``dcdcb"} temp=“dcdcb"。
-
将出栈的字母反转, temp = “bcdcd" \textit{temp} = \text{``bcdcd"} temp=“bcdcd"。
-
将数字栈的栈顶元素 k = 3 k = 3 k=3 出栈,执行 3 3 3 次将 “bcdcd" \text{``bcdcd"} “bcdcd" 入字符串栈。字符串栈为 “abcdcdbcdcdbcdcd" \text{``abcdcdbcdcdbcdcd"} “abcdcdbcdcdbcdcd",数字栈为 [ ] [] []。
下标 10 10 10 和 11 11 11 处的字符都是字母,将字母入字符串栈。字符串栈为 “abcdcdbcdcdbcdcdef" \text{``abcdcdbcdcdbcdcdef"} “abcdcdbcdcdbcdcdef",数字栈为 [ ] [] []。
解码字符串为 “abcdcdbcdcdbcdcdef" \text{``abcdcdbcdcdbcdcdef"} “abcdcdbcdcdbcdcdef"。
代码
class Solution {
public String decodeString(String s) {
StringBuffer sb = new StringBuffer();
Deque<Integer> stack = new ArrayDeque<Integer>();
int num = 0;
int length = s.length();
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (Character.isDigit(c)) {
num = num * 10 + c - '0';
} else if (Character.isLetter(c)) {
sb.append(c);
} else if (c == '[') {
stack.push(num);
num = 0;
sb.append(c);
} else {
int top = sb.length() - 1;
StringBuffer temp = new StringBuffer();
while (sb.charAt(top) != '[') {
temp.append(sb.charAt(top));
sb.deleteCharAt(top--);
}
sb.deleteCharAt(top--);
temp.reverse();
int k = stack.pop();
for (int j = 0; j < k; j++) {
sb.append(temp);
}
}
}
return sb.toString();
}
}
复杂度分析
-
时间复杂度: O ( n e + n d ) O(n_e + n_d) O(ne+nd),其中 n e n_e ne 是编码字符串 s s s 的长度, n d n_d nd 是解码字符串的长度。需要遍历编码字符串 s s s 一次,解码过程中需要遍历解码字符串。
-
空间复杂度: O ( n d ) O(n_d) O(nd),其中 n d n_d nd 是解码字符串的长度。空间复杂度主要取决于栈空间。
解法二
思路和算法
解法一使用栈实现解码,也可以用递归代替栈。
解法一的栈用于遇到数字和方括号的情况,递归处理的情况也是遇到数字和方括号的情况。从左到右遍历编码字符串 s s s,遇到数字时,得到重复次数,然后定位到左方括号的右边一个字符,调用递归。递归的终止条件是遇到右方括号,此时一对方括号遍历结束,根据方括号内的字母和重复次数解码。
代码
class Solution {
int index = 0;
public String decodeString(String s) {
return recurse(s).toString();
}
public StringBuffer recurse(String s) {
StringBuffer sb = new StringBuffer();
int length = s.length();
while (index < length) {
char c = s.charAt(index);
if (c == ']') {
return sb;
} else if (Character.isDigit(c)) {
int num = 0;
while (index < length && Character.isDigit(s.charAt(index))) {
num = num * 10 + s.charAt(index) - '0';
index++;
}
index++;
StringBuffer temp = recurse(s);
for (int i = 0; i < num; i++) {
sb.append(temp);
}
} else {
sb.append(c);
}
index++;
}
return sb;
}
}
复杂度分析
-
时间复杂度: O ( n e + n d ) O(n_e + n_d) O(ne+nd),其中 n e n_e ne 是编码字符串 s s s 的长度, n d n_d nd 是解码字符串的长度。需要遍历编码字符串 s s s 一次,解码过程中需要遍历解码字符串。
-
空间复杂度: O ( n d ) O(n_d) O(nd),其中 n d n_d nd 是解码字符串的长度。空间复杂度主要取决于递归调用的栈空间和存储解码字符串的 StringBuffer \texttt{StringBuffer} StringBuffer 类型的对象。