394. 字符串解码
题目描述
给定一个经过编码的字符串,返回它解码后的字符串。编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
示例 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"
思路一:利用栈
两个栈,一个存储Integer元素,记录重复次数。一个存储StringBuilder元素,String元素是当前数字和之前的上一个左括号之间的字符串,可以理解为下个重复子串的前缀;两个栈的栈顶元素是配套使用的
使用一个multi 变量记录重复次数,一个pefix记录某个括号之前的前缀
遍历字符串,
- 如果遇到数字,更新multi,
- 如果遇到左括号,把multi和prefix入栈, 并且multi和prefix都置为空,方便内层记录重复次数和prefix
- 如果遇到右括号,弹出两个栈的栈顶元素,重复multi次,把当前prefix中的内容附加到String栈的栈顶元素后面
- 把经过重复后的字符串更新为前缀
- 如果是其他字符,prefix上加上这个字符
1 class Solution { 2 public String decodeString(String s) { 3 // 两个栈,一个存储Integer元素,一个存储String元素,两个栈的栈顶元素是配套使用的 4 Stack<Integer> multiStack = new Stack<Integer>(); 5 Stack<StringBuilder> prefixStack = new Stack<>(); // String元素是当前数字和之前的上一个左括号之间的字符串,可以理解为前缀 6 int multi = 0; // 记录重复次数 7 StringBuilder prefix = new StringBuilder(); // 记录下个数字和上个左括号之间的数字 8 9 for(char c : s.toCharArray()){ 10 11 // 如果遇到数字,更新multi 12 if(c <= '9' && c >= '0'){ 13 multi = multi * 10 + (c - '0'); // 因为重复次数可能大于10, 所以可能会有多个连续的数字 14 }else if(c == '['){ 15 // 如果遇到左括号,把multi和res入栈, 并且multi和res都置为空,方便内层记录重复次数和res 16 multiStack.push(multi); 17 prefixStack.push(prefix); 18 multi = 0; 19 prefix = new StringBuilder(); 20 }else if(c == ']'){ 21 // 如果遇到右括号,弹出两个栈的栈顶元素,重复multi次,把当前prefix中的内容附加到String栈的栈顶元素后面 22 int multiCnt = multiStack.pop(); 23 StringBuilder res = prefixStack.pop(); 24 for(int i = 0; i < multiCnt; i++){ 25 res.append(prefix.toString()); 26 } 27 prefix = res; // 把经过重复后的字符串更新为前缀 28 }else{ 29 prefix.append(c); // 如果是其他字符,prefix上加上这个字符 30 } 31 } 32 return prefix.toString(); 33 } 34 }
leetcode 执行用时:0 ms > 100.00%, 内存消耗:36.5 MB > 95.51%
复杂度分析:
时间复杂度:遍历了一次字符串,所以时间复杂度为O(n)
空间复杂度:空间复杂度来源于栈的大小,栈的大小取决于方括号的层数,辅助栈在极端情况下需要线性空间,例如
2[2[2[a]]]
。所以空间复杂度为O(n)。思路二:递归写法
递归某一层,获取到某个方括号对的所有内容,第一层其实可以也当做是在一个方括号对中。
重复次数保存在上一层,这样当求出某一层方括号的所有内容后就可以返回到上一层把下层结果重复添加到上一层的结果串中。
因为下一层的子串是通过递归获取到的,所以这段子串已经被递归遍历过了,所以返回到上一层的时候必须跳过这段子串,所以需要在下一层返回该层的结束位置,这样上一层下次从这个结束位置开始继续往后遍历
- 碰到数字,更新multi变量
- 碰到左括号,递归获取该方括号对的内容,将该内容拼接到当前层次的结果串中;并获取到右方括号的位置、multi置为空
- 如果碰到右括号,直接返回当前层次的结果串和结束下标
- 如果是其他字符,添加到当前层次的结果串中
- 返回第一层的结果串
1 class Solution { 2 public String decodeString(String s) { 3 // 递归解法 4 return dfs(s, 0)[0]; 5 } 6 7 public String[] dfs(String s, int i){ 8 StringBuilder res = new StringBuilder(); 9 int multi = 0; 10 while(i < s.length()){ 11 // 碰到数字,更新multi变量 12 if(s.charAt(i) >= '0' && s.charAt(i) <= '9'){ 13 multi = multi * 10 + (s.charAt(i) - '0'); 14 }else if(s.charAt(i) == '['){ 15 // 碰到左括号,递归获取该方括号对的内容,将该内容拼接到当前层次的结果串中 16 String[] tmp = dfs(s, i+1); 17 for(int j = 0; j < multi; j++){ 18 res.append(tmp[1]); 19 } 20 // 并获取到右方括号的位置 21 i = Integer.parseInt(tmp[0]); 22 // multi置为空 23 multi = 0; 24 }else if(s.charAt(i) == ']'){ 25 // 如果碰到右括号,直接返回当前层次的结果串和结束下标 26 return new String[]{"" + i, res.toString()}; 27 }else{ // 如果是其他字符,添加到当前层次的结果串中 28 res.append(s.charAt(i)); 29 } 30 i++; 31 } 32 // 返回第一次层的结果串 33 return new String[]{res.toString()}; 34 } 35 }
leetcode 执行用时:3 ms > 32.49%, 内存消耗:36.8 MB > 56.98%,因为递归栈的开销,无论是时间还是空间效率都比思路一低一些
复杂度分析:
时间复杂度:遍历了一遍字符串,所以时间复杂度为O(n)
空间复杂度:取决于递归栈的深度,最坏情况下深度是线性的,例如
2[2[2[a]]],所以空间复杂度为O(n)