力扣-字符串解码

思路分析

  1. 核心数据结构:使用两个栈,分别存储「重复次数 k」和「待拼接的字符串」;
  2. 遍历逻辑:
    • 遇到数字:拼接完整的数字(处理多位数,如 100 [abc]);
    • 遇到左括号[:将当前数字和当前字符串分别入栈,然后重置数字和字符串;
    • 遇到右括号]:弹出栈顶的数字和字符串,将当前字符串重复对应次数后拼接到弹出的字符串后;
    • 遇到普通字符:直接拼接到当前字符串。

代码实现

java 复制代码
public String decodeString(String s) {
        // 存储重复次数的栈
        Stack<Integer> countStack = new Stack<>();
        // 存储待拼接字符串的栈
        Stack<String> strStack = new Stack<>();
        // 当前拼接的字符串
        StringBuilder currentStr = new StringBuilder();
        // 当前累积的数字(处理多位数)
        int currentNum = 0;

        // 遍历每个字符
        for (char c : s.toCharArray()) {
            if (Character.isDigit(c)) {
                // 处理多位数,如"123[abc]"中的123
                currentNum = currentNum * 10 + (c - '0');
            } else if (c == '[') {
                // 左括号:将当前数字和字符串入栈,重置
                countStack.push(currentNum);
                strStack.push(currentStr.toString());
                currentNum = 0;
                currentStr.setLength(0); // 清空当前字符串
            } else if (c == ']') {
                // 右括号:弹出数字和字符串,拼接重复后的结果
                int repeatCount = countStack.pop();
                String prevStr = strStack.pop();
                // 重复当前字符串repeatCount次
                StringBuilder repeatedStr = new StringBuilder(prevStr);
                for (int i = 0; i < repeatCount; i++) {
                    repeatedStr.append(currentStr);
                }
                // 更新当前字符串为拼接后的结果
                currentStr = repeatedStr;
            } else {
                // 普通字符:直接拼接
                currentStr.append(c);
            }
        }

        return currentStr.toString();
    }

复杂度分析

  • 时间复杂度 O (n)(每个字符仅遍历一次,重复拼接的总次数等于最终字符串长度)
  • 空间复杂度 O (n)(栈的深度不超过嵌套层数)。

具体分析

一、先明确问题本质

编码规则:k[encoded_string] → 括号内的字符串重复k次,且支持嵌套(如3[a2[bc]])。

核心需求:把嵌套的、带重复次数的编码字符串,还原成普通字符串。

举个例子:

  • 输入3[a2[bc]] → 先解内层2[bc]得到bcbc → 再解外层3[abcbc]得到abcbcabcbcabcbc

二、核心难点分析

  1. 嵌套结构处理 :括号是嵌套的,必须先解内层、再解外层(如先处理2[bc],再处理外层的3[...]);
  2. 多位数处理 :重复次数k可能是多位数(如100[abc]),不能把100拆成单个数字处理;
  3. 字符串拼接效率 :如果用普通String拼接,频繁创建对象会导致性能低下(尤其输出长度接近10^5时)。

三、解法选择:为什么用「栈」?

栈的核心特性是后进先出(LIFO),完美匹配嵌套结构的"先处理内层、再处理外层"需求:

  • 遇到左括号[:相当于"进入内层",需要保存当前的"重复次数"和"已拼接的字符串"(前缀);
  • 遇到右括号]:相当于"退出内层",需要取出之前保存的"重复次数"和"前缀",把内层字符串重复后拼接到前缀后。

可以把栈理解为"临时保存现场"的工具:进入内层前,把外层的状态(次数+前缀)存起来;退出内层后,取出外层状态,合并内层结果。

四、分步拆解核心思路(结合示例3[a2[bc]]

我们用两个栈配合:

  • countStack:存储"待使用的重复次数"(如3、2);
  • strStack:存储"进入内层前的前缀字符串"(如空字符串、"a");
    再用两个变量记录"当前状态":
  • currentNum:累积当前的数字(处理多位数);
  • currentStr:拼接当前层的普通字符/内层解码结果。
步骤1:初始化
复制代码
countStack = []  # 空栈,存重复次数
strStack = []    # 空栈,存前缀字符串
currentNum = 0   # 初始数字为0
currentStr = ""  # 初始字符串为空
步骤2:遍历每个字符(以3[a2[bc]]为例)

逐个处理字符:3[a2[bc]]

字符1:3(数字)
  • 逻辑:累积多位数(不能直接入栈,因为可能是多位数);
  • 计算:currentNum = 0 * 10 + (3 - '0') = 3
  • 状态:currentNum=3,其余不变。
字符2:[(左括号)
  • 逻辑:进入内层,保存"外层状态"到栈中,然后重置当前状态;
  • 操作:
    1. countStack.push(3) → 把外层重复次数3存入栈;
    2. strStack.push("") → 外层前缀是空字符串,存入栈;
    3. currentNum = 0 → 重置数字,准备处理内层的数字;
    4. currentStr = "" → 重置字符串,准备拼接内层字符;
  • 状态:
    countStack=[3]strStack=[""]currentNum=0currentStr=""
字符3:a(普通字符)
  • 逻辑:普通字符直接拼接到当前字符串;
  • 操作:currentStr += "a"currentStr="a"
  • 状态:currentStr="a",其余不变。
字符4:2(数字)
  • 逻辑:累积多位数;
  • 计算:currentNum = 0 * 10 + (2 - '0') = 2
  • 状态:currentNum=2,其余不变。
字符5:[(左括号)
  • 逻辑:进入更深层,保存当前层状态到栈,重置当前状态;
  • 操作:
    1. countStack.push(2) → 把内层重复次数2存入栈;
    2. strStack.push("a") → 当前层前缀是"a",存入栈;
    3. currentNum = 0 → 重置数字;
    4. currentStr = "" → 重置字符串;
  • 状态:
    countStack=[3,2]strStack=["", "a"]currentNum=0currentStr=""
字符6:b(普通字符)
  • 逻辑:拼接到当前字符串;
  • 操作:currentStr += "b"currentStr="b"
  • 状态:currentStr="b"
字符7:c(普通字符)
  • 逻辑:拼接到当前字符串;
  • 操作:currentStr += "c"currentStr="bc"
  • 状态:currentStr="bc"
字符8:](右括号)
  • 逻辑:退出内层,取出栈中保存的状态,拼接重复后的字符串;
  • 操作:
    1. repeatCount = countStack.pop() → 弹出内层次数2;
    2. prevStr = strStack.pop() → 弹出内层前缀"a";
    3. currentStr("bc")重复2次,拼接到prevStr后 → prevStr + "bc"*2 = "a" + "bcbc" = "abcbc"
    4. currentStr = "abcbc" → 更新当前字符串为拼接结果;
  • 状态:
    countStack=[3]strStack=[""]currentNum=0currentStr="abcbc"
字符9:](右括号)
  • 逻辑:退出外层,取出栈中保存的外层状态,拼接重复后的字符串;
  • 操作:
    1. repeatCount = countStack.pop() → 弹出外层次数3;
    2. prevStr = strStack.pop() → 弹出外层前缀"";
    3. currentStr("abcbc")重复3次,拼接到prevStr后 → "" + "abcbc"*3 = "abcbcabcbcabcbc";
    4. currentStr = "abcbcabcbcabcbc"
  • 状态:
    countStack=[]strStack=[]currentNum=0currentStr="abcbcabcbcabcbc"
步骤3:遍历结束,返回结果

最终currentStr就是解码后的字符串:abcbcabcbcabcbc

五、关键细节补充

1. 为什么用StringBuilder而不是String
  • String是不可变对象,每次拼接(+=)都会创建新对象,比如重复1000次会创建1000个对象,性能极低;
  • StringBuilder是可变的,所有拼接操作都在同一个对象上完成,时间复杂度从O(n²)降到O(n),满足输出长度≤10^5的要求。
2. 多位数处理的逻辑为什么是currentNum * 10 + (c - '0')
  • 字符'0'-'9'的ASCII码是连续的,c - '0'可以把字符转成对应的数字(如'3' - '0' = 3);
  • 累积逻辑:比如处理123时,先算0*10+1=1,再算1*10+2=12,最后算12*10+3=123,完美还原多位数。
3. 栈的"保存现场"逻辑是核心
操作时机 保存的内容 目的
左括号[ currentNum(次数)+ currentStr(前缀) 进入内层前,把外层的状态存起来,避免丢失
右括号] 弹出次数+前缀,拼接currentStr*次数 退出内层后,把内层结果合并到外层

六、思路总结(核心要点)

  1. 结构匹配:栈的"后进先出"完美适配括号的嵌套结构,先解内层、再解外层;
  2. 状态管理:用两个栈保存"外层状态",用两个变量记录"当前层状态",分离不同层级的上下文;
  3. 细节优化
    • 多位数累积:currentNum * 10 + (c - '0')
    • 高效拼接:StringBuilder替代String
  4. 遍历逻辑
    • 数字→累积;左括号→存状态+重置;右括号→取状态+拼接;普通字符→直接拼。

这个思路的本质是"分层处理":把嵌套的编码字符串拆成多个层级,用栈隔离不同层级的状态,逐个层级解码后合并,最终得到完整结果。

相关推荐
菜鸟233号2 小时前
力扣494 目标和 java实现
java·数据结构·算法·leetcode
Knight_AL2 小时前
docx4j vs LibreOffice:Java 中 Word 转 PDF 的性能实测
java·pdf·word
悟道|养家2 小时前
基于L1/L2 缓存访问速度的角度思考数组和链表的数据结构设计以及工程实践方案选择(2)
java·开发语言·缓存
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章9-均值滤波
人工智能·opencv·算法·计算机视觉·均值算法
虫小宝2 小时前
微信群发消息API接口对接中Java后端的请求参数校验与异常反馈优化技巧
android·java·开发语言
万行2 小时前
机器学习&第六.七章决策树,集成学习
人工智能·python·算法·决策树·机器学习·集成学习
麦兜*2 小时前
Spring Boot整合Swagger 3.0:自动生成API文档并在线调试
java·spring boot·后端
sin_hielo2 小时前
leetcode 1266
数据结构·算法·leetcode
星火开发设计2 小时前
C++ deque 全面解析与实战指南
java·开发语言·数据结构·c++·学习·知识