LeetCode 439 - 三元表达式解析器


文章目录

摘要

题目给出一个嵌套的三元表达式(条件 ? expr1 : expr2),条件仅为字符 'T''F',表达式中的值为单字符(比如 '0'..'9'、字母或另一个三元表达式)。任务是解析该字符串,返回最终的值(仍为单字符)。

这道题看上去像是写一个小解析器,但用一个简单的栈 + 从右向左扫描技巧,就能优雅地解决所有嵌套情况,代码短、易理解。

描述

输入是一个合法的三元表达式字符串,例如:

txt 复制代码
"T?2:3"

返回 "2"

更复杂的嵌套例子:

txt 复制代码
"T?2:F?3:4"

先从语义看:T ? 2 : (F ? 3 : 4),结果是 2

或者

txt 复制代码
"F?1:T?4:5"

这个等价于 F ? 1 : (T ? 4 : 5),结果是 4

注意表达式里条件只会是 TF,值都是单个字符(题目保证),因此我们可以把每个操作符 ?: 看作定界符,解析时关键是如何正确匹配嵌套的 ? : 组合。

题解答案

常见且高效的思路是 从右向左扫描 字符串,并用栈保存"已解析的表达式值":

  • 从右向左有个好处:当我们遇到 ? 时,栈顶正好保存着 ? 对应的 true 分支表达式(最早被推入栈)和 false 分支表达式(次之);

  • 因为我们只关心每次三元表达式的结果(它本身是一个单字符表达式),我们可以把解析过的子表达式的结果也当作一个值压入栈;

  • 具体操作:

    • 遍历字符,从右往左;

    • 遇到 : ------ 跳过(不需要压栈);

    • 遇到普通字符(条件 T/F 以外的字符) ------ 将其作为字符串压入栈;

    • 遇到 ? ------ 说明当前是形如 cond ? expr1 : expr2 的分割点:

      • 弹出栈顶作为 expr1(true 分支的值)
      • 再弹出作为 expr2(false 分支的值)
      • 读取 ? 左边的字符作为 cond(注意我们从右向左扫描,cond 就在当前位置 i-1
      • 根据 cond 选择 expr1expr2,将选择的结果压回栈
      • 跳过那个已经读取的 cond 字符(i 减 1)
  • 最后,栈顶即为最终结果(单字符字符串)

这个思路简洁、直观,能正确处理任意嵌套深度。

可运行 Swift 实现

下面是一个完整、可直接粘到 Playground 或 Swift 文件中运行的实现,并附带几个测试示例:

swift 复制代码
import Foundation

class Solution {
    func parseTernary(_ expression: String) -> String {
        if expression.isEmpty { return "" }
        let chars = Array(expression)
        var stack: [String] = []
        var i = chars.count - 1

        while i >= 0 {
            let ch = chars[i]
            if ch == ":" {
                // 冒号用于分隔,不入栈
            } else if ch == "?" {
                // 遇到 '?', 取出 trueExpr 和 falseExpr(栈中存的是已解析的表达式结果)
                guard !stack.isEmpty else { return "" }
                let trueExpr = stack.removeLast()
                guard !stack.isEmpty else { return "" }
                let falseExpr = stack.removeLast()

                // 条件在 '?' 左侧一个字符
                let condIndex = i - 1
                guard condIndex >= 0 else { return "" }
                let cond = chars[condIndex]

                // 选取合适的分支并将结果入栈
                let chosen = (cond == "T") ? trueExpr : falseExpr
                stack.append(chosen)

                // 跳过条件字符 (cond),因为已经处理
                i -= 1
            } else {
                // 普通字符(数字、字母 or 'T'/'F' 作为 condition 需要在遇到 '?' 时才读取)
                stack.append(String(ch))
            }

            i -= 1
        }

        // 解析完成后,栈顶就是最终结果(单字符)
        return stack.last ?? ""
    }
}

// Demo 测试
let solver = Solution()

let tests = [
    "T?2:3",
    "F?1:T?4:5",
    "T?2:F?3:4",
    "T?T?F:5:3", // 嵌套 condition/value 情况
    "F?T?1:2:T?3:4" // 更多嵌套组合
]

for expr in tests {
    print("\(expr) -> \(solver.parseTernary(expr))")
}

题解代码详解(逐行讲解)

  1. 把输入表达式 String 转成 Array<Character>,以便按索引访问。

  2. 使用 stack: [String] 存放已经解析出来的子表达式结果(都作为单字符字符串存放)。

  3. 从字符串末尾开始向前扫描:

    • 遇到 ':':只是分隔符,忽略;

    • 遇到普通字符(数字或字母或 T/F),把它作为"一个值"压入栈;

    • 遇到 ?

      • 弹出两个值 trueExpr(栈顶)和 falseExpr(次顶)。注意顺序:因为我们从右向左扫描,最先遇到并压入栈的是 expr2(false),随后是 expr1(true),所以弹出的第一个为 trueExpr,第二个为 falseExpr
      • 读取 ? 左边的那个字符作为条件 cond(因为我们是从右到左,此时 i-1 是条件字符);
      • 根据条件选择 trueExprfalseExpr 并把选择的字符串压入栈(作为该子表达式的新值);
      • 记得 i -= 1 跳过条件字符;
  4. 扫描完毕后,栈顶就是最终结果。

关键点总结:

  • 从右向左扫描是核心技巧。它保证当你遇到 ? 时,对应的两个分支表达式已经被解析并保存在栈里;
  • 我们只保存"每个子表达式的最终值"而不是它的内部结构,这一点受益于题目保证表达式最终结果为单字符;
  • 索引管理要小心:遇到 ? 时需要额外跳过左侧的条件字符(i -= 1)。

示例测试及结果

运行上面 Demo,你会看到可能类似的输出(具体结果按表达式而定):

txt 复制代码
T?2:3 -> 2
F?1:T?4:5 -> 4
T?2:F?3:4 -> 2
T?T?F:5:3 -> 3
F?T?1:2:T?3:4 -> 3

来解读几个示例:

  • T?2:3:条件为 T,结果选 2
  • F?1:T?4:5:等价 F ? 1 : (T ? 4 : 5),条件 F,返回后半 (T ? 4 : 5) 的结果 4
  • T?2:F?3:4:等价 T ? 2 : (F ? 3 : 4),条件 T,返回 2
  • T?T?F:5:3:解析为 T ? (T ? F : 5) : 3。内层 (T ? F : 5) 返回 F,外层 T ? F : 3 返回 F,使用我们的实现会以字符串 "F" 返回;但上面的 demo 弹出的可能为字符代表值,视测试表达式内容而定。

(注意:例子中混合使用字母和数字作为值都可,最终输出是对应的字符串)

时间复杂度

我们每个字符最多被访问一次,压入/弹出栈都是 O(1) 操作,因此整体时间复杂度为:

O(n),其中 n 是表达式长度。

空间复杂度

额外空间主要用于栈,最坏情况下(比如没有 ?/:,或者很多独立的值)需要存放 O(n) 个单字符字符串。

空间复杂度:O(n)

总结

  • 这道题虽然看起来像写解析器,但用从右向左扫描 + 栈的技巧可以写出简洁高效的解法;
  • 关键点是:遇到 ? 时,栈上已经有 true 分支与 false 分支的结果,可以直接弹出并根据条件选择;
  • 算法时间复杂度线性,易于实现,适合在面试和工程场景中使用;
  • 如果表达式里的值不再是单字符(比如多位数或者包含复杂子表达式),思路上仍然类似,只是 token 化和栈元素的管理要更复杂一些(需要处理多字符 token)。
相关推荐
小殊小殊44 分钟前
重磅!DeepSeek发布V3.2系列模型!
论文阅读·人工智能·算法
裤裤兔1 小时前
利用matlab进行FDR校正的实现方式
数据结构·算法·matlab·多重比较矫正·校正·fdr
野蛮人6号1 小时前
力扣热题100道之31下一个排列
算法·leetcode·职场和发展
子一!!1 小时前
哈希桶,元素插入逻辑实现
算法·哈希算法
敲代码的嘎仔1 小时前
LeetCode面试HOT100——160. 相交链表
java·学习·算法·leetcode·链表·面试·职场和发展
吃着火锅x唱着歌1 小时前
LeetCode 454.四数相加II
算法·leetcode·职场和发展
敲代码的嘎仔1 小时前
LeetCode面试HOT100—— 206. 反转链表
java·数据结构·学习·算法·leetcode·链表·面试
自然语1 小时前
深度学习时代结束了,2025年开始只剩下轮廓
数据结构·人工智能·深度学习·学习·算法
海天一色y1 小时前
Leetcode07-整数反转
算法