在问 · 算法题解

241. 为运算表达式设计优先级

中等动态规划分治 + 记忆化力扣原题 ↗

题目描述

输入一个字符串 expression,由非负整数和运算符 +、-、* 组成。

可以给这个表达式随意加括号,从而决定各个运算的先后次序。每一种不同的加括号方式都算出一个值。

返回所有加括号方式得到的值,顺序不限。不同方式算出相同的值时,要重复列出。

测试数据保证所有结果都在 32 位整数范围内,结果个数不超过 10^4。

示例 1
输入:expression = "2-1-1"
输出:[2,0]

解释:(2-(1-1)) = 2,((2-1)-1) = 0。

示例 2
输入:expression = "2*3-4*5"
输出:[-34,-10,-14,-10,10]

解释:五种加括号方式:(2*(3-(4*5))) = -34,((2*(3-4))*5) = -10,((2*3)-(4*5)) = -14,(2*((3-4)*5)) = -10,(((2*3)-4)*5) = 10。

数据范围

  • 1 <= expression.length <= 20
  • expression 只含数字和运算符 +、-、*。
  • 表达式中的每个整数都在 [0, 99] 之间。
  • 整数前面不会带表示正负的 + 或 -。

解题思路:分治 + 记忆化

分治:任何加括号方案都有“最后一步计算”的那个运算符。枚举每个运算符 c 作为最后一步,递归求出左半边和右半边各自所有可能的值,再两两用 c 组合。纯数字直接返回自身。同一个子串可能被多次求解,用 memo 缓存结果。

为什么正确

go(s) 返回子表达式 s 所有加括号方案的值(可重复),数量等于方案数。

复杂度分析

结果数是卡特兰数,时间和空间与结果数量同阶。

易错点

数字可能是两位数,不能按单个字符切分;结果要保留重复值,不同加括号方式得到相同的值也要各算一个。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def diffWaysToCompute(self, expression: str) -> List[int]:
        memo = {}
        def go(s):
            if s in memo:
                return memo[s]
            if s.isdigit():
                return [int(s)]
            res = []
            for i, c in enumerate(s):
                if c in '+-*':
                    for a in go(s[:i]):
                        for b in go(s[i + 1:]):
                            res.append(a + b if c == '+' else a - b if c == '-' else a * b)
            memo[s] = res
            return res
        return go(expression)

载入编辑器,自己跑一遍 →

其他解法

两种方法的状态是同一个:子表达式的所有可能结果。分治自顶向下按运算符切分,区间 DP 自底向上按区间长度填表。结果列表的顺序不同,但多重集一致。

解法 2:区间动态规划 时间 O(2^n),空间 O(2^n)

先把表达式拆成数字和运算符交替的列表,共 m 个数字。dp[i][j] 表示第 i 到第 j 个数字组成的子表达式所有可能的结果。按区间长度从小到大计算:枚举最后一步计算的运算符位置 k,把 dp[i][k] 和 dp[k+1][j] 中的结果两两组合。答案是 dp[0][m-1]。

不需要递归,状态用数字下标表示,比用子串作记忆化的键更省。结果数量本身是卡特兰数级别,整体复杂度和分治相同。

class Solution:
    def diffWaysToCompute(self, expression: str) -> List[int]:
        nums, ops = [], []
        num = 0
        for c in expression:
            if c.isdigit():
                num = num * 10 + int(c)
            else:
                nums.append(num)
                ops.append(c)
                num = 0
        nums.append(num)
        m = len(nums)
        dp = [[[] for _ in range(m)] for _ in range(m)]
        for i in range(m):
            dp[i][i] = [nums[i]]
        for length in range(2, m + 1):
            for i in range(m - length + 1):
                j = i + length - 1
                for k in range(i, j):
                    op = ops[k]
                    for a in dp[i][k]:
                        for b in dp[k + 1][j]:
                            dp[i][j].append(a + b if op == '+' else a - b if op == '-' else a * b)
        return dp[0][m - 1]

动态规划 专题的其他题目

查看 动态规划 全部题目 →