(leetcode)力扣100 71字符串解码(栈(两种)||递归)

题目

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: 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"。

任务全部完成。
相关推荐
重生之后端学习2 小时前
105. 从前序与中序遍历序列构造二叉树
java·数据结构·后端·算法·深度优先
样例过了就是过了2 小时前
LeetCodere热题100 最小覆盖子串
数据结构·算法·leetcode
追随者永远是胜利者2 小时前
(LeetCode-Hot100)10. 正则表达式匹配
java·算法·leetcode·go
jimy12 小时前
从Windows terminal里面的输出内容中截取trim IP 地址,再更新到.ssh/config文件里面
windows·tcp/ip·ssh
We་ct2 小时前
LeetCode 146. LRU缓存:题解+代码详解
前端·算法·leetcode·链表·缓存·typescript
烟花落o2 小时前
【数据结构系列03】链表的回文解构、相交链表
数据结构·算法·链表·刷题
努力学算法的蒟蒻2 小时前
day87(2.16)——leetcode面试经典150
数据结构·leetcode·面试
追随者永远是胜利者2 小时前
(LeetCode-Hot100)17. 电话号码的字母组合
java·算法·leetcode·职场和发展·go
不想看见4042 小时前
Shortest Bridge -- 广度优先搜索 --力扣101算法题解笔记
算法·leetcode·宽度优先