题目描述

原题:282. 给表达式添加运算符。

在数字串的相邻数字之间选择拼接,或插入+、-、*,返回值等于target的所有表达式。数字不能有多余前导零,运算遵循乘法优先。

示例 1:

输入:num = "123", target = 6
输出:["1+2+3","1*2*3"]
解释:两条表达式的计算结果均为 6,数字相对顺序保持不变。

提示:

  • 输入为十进制数字字符串;只能插入 +、-、* 或拼接相邻数字。操作数不得有多余前导零,乘法优先于加减法。

题意分析

保持数字串的原始顺序,在相邻数字之间选择不插入运算符,或插入 +、-、*,返回所有计算结果等于 target 的表达式。每个数字都必须使用,不能重新排列或加入括号。

连续拼接的数字组成一个操作数,多位操作数不能有前导零,但单独的 0 合法。表达式按乘法优先于加减计算,不能简单地把所有运算从左到右累计。

解法:回溯切分数字并维护最后乘法项

核心思路

[!blue]

从当前未使用的位置 pos 开始,枚举下一个操作数的结束位置,决定拼接多少位;第一段直接作为表达式开头,之后每一段分别尝试加、减、乘。每种表达式都有确定的操作数分段和段间运算符,这样的枚举能覆盖全部合法选择。

递归除了保存表达式文本,还维护当前总值 value 和最后一个带符号乘法项 last。last 不是简单的最后一个数字,而是最后一整段连续乘法已经贡献的值,包含它前面的正负号。

接上加法时,新项就是 operand,因此更新为 value + operand,并令 last = operand;接上减法时,新项为 -operand,更新总值并把负号保存在 last 中。

接上乘法时,应该只扩大最后一项,而不是把整个前缀相乘。因为原总值可以写成“前面已经完成的项 + last”,先撤销旧贡献,再加入新贡献即可:newValue = value - last + last * operand,同时把 last 更新成 last * operand。这个替换也能继续处理一串乘法,或减号后面的乘法链,无需为每个候选另写表达式解析器。

枚举操作数时逐位累积数值。若段首为 0,只允许取这一位,后续更长前缀全部非法,可以直接停止本轮延长。每次递归都推进到下一未使用位置;用完全部数字时才判断总值,等于目标就保存文本,否则结束该分支。

解题步骤

  1. 从位置 0、空表达式开始,逐步枚举下一段数字,使用 64 位变量保存操作数及累计结果。
  2. 段首为零时只保留单字符零,其余情况按十进制逐位扩大操作数。
  3. 第一段不加运算符,直接初始化 value 和 last。
  4. 后续段分别尝试加、减、乘;加减开始新项,乘法用撤销旧末项再加入新乘积的公式更新。
  5. 每次递归从该操作数末尾之后继续;全部字符用完且总值等于目标时加入答案。

代码实现

class Solution {
    public List<String> addOperators(String num, int target) {
        List<String> result = new ArrayList<>();

        if (!num.isEmpty()) {
            dfs(num, target, 0, 0, 0, "", result);
        }

        return result;
    }

    private void dfs(
            String s,
            long target,
            int pos,
            long value,
            long last,
            String expression,
            List<String> result) {
        if (pos == s.length()) {
            if (value == target) {
                result.add(expression);
            }

            return;
        }

        long operand = 0;

        for (int end = pos; end < s.length(); end++) {
            if (end > pos && s.charAt(pos) == '0') {
                break;
            }

            operand = operand * 10 + s.charAt(end) - '0';
            String token = s.substring(pos, end + 1);

            if (pos == 0) {
                dfs(s, target, end + 1, operand, operand, token, result);
            } else {
                dfs(s, target, end + 1, value + operand, operand, expression + "+" + token, result);
                dfs(
                        s,
                        target,
                        end + 1,
                        value - operand,
                        -operand,
                        expression + "-" + token,
                        result);
                dfs(
                        s,
                        target,
                        end + 1,
                        value - last + last * operand,
                        last * operand,
                        expression + "*" + token,
                        result);
            }
        }
    }
}
func addOperators(num string, target int) []string {
    result := []string{}
    var dfs func(int, int64, int64, string)
    dfs = func(pos int, value, last int64, expression string) {
        if pos == len(num) {
            if value == int64(target) {
                result = append(result, expression)
            }
            return
        }
        operand := int64(0)
        for end := pos; end < len(num); end++ {
            if end > pos && num[pos] == '0' {
                break
            }
            operand = operand*10 + int64(num[end]-'0')
            token := num[pos : end+1]
            if pos == 0 {
                dfs(end+1, operand, operand, token)
            } else {
                dfs(end+1, value+operand, operand, expression+"+"+token)
                dfs(end+1, value-operand, -operand, expression+"-"+token)
                dfs(end+1, value-last+last*operand, last*operand, expression+"*"+token)
            }
        }
    }
    if len(num) > 0 {
        dfs(0, 0, 0, "")
    }
    return result
}

复杂度分析

设数字串长度为 n,所有返回表达式的字符总量为 S。

  • 时间复杂度:$O(n\cdot4^n)$ 上界,每个数字间隙最多选择拼接或三个运算符,当前实现的子串与表达式拼接还包含线性字符处理成本。
  • 空间复杂度:不计答案为 $O(n^2)$ 上界。递归深度为 $O(n)$,当前调用链上的多个不可变表达式前缀同时保留;返回结果另占 $O(S)$。原题 n <= 10,64 位整数足以保存这里的操作数和中间运算。

关键点总结

[!green]

  • 切分数字决定操作数,选择符号决定运算,两层枚举完整覆盖表达式。
  • last 是带符号的末尾乘法项,保留它才能在追加乘法时只修正该项贡献。
  • 前导零限制作用于每个操作数,单独的零不需要禁止。
  • 文本用于返回答案,数值状态用于直接判定结果,二者同步构造。

易错点总结

[!yellow]

  • 乘法直接计算 value * operand,会把前面其他加减项也一起相乘。
  • 减法后仍把 last 设为正数,下一次乘法就会撤销错误符号的贡献。
  • 连乘后不更新 last,再追加乘法时只能回退其中一部分。
  • 在第一个操作数之前添加符号,会生成题目未要求的带前置运算符表达式。
  • 允许以零开头的多位操作数,或把单独零也排除,都会改变合法候选集合。
  • 用 32 位变量保存拼接出的操作数或中间值,即使目标参数是 int,中途结果也可能超出它的范围。

相似题目

题目 难度 关联与区别
93. 复原 IP 地址 中等 同样按位置切分数字并排除多位前导零,本题再为分段之间选择运算符。
227. 基本计算器 II 中等 都需要处理乘法优先级;本题在生成表达式时回退最后一项,无需每次重新解析整串。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/74106067
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!