LeetCode —— 282.给表达式添加运算符(JavaScript)

181 阅读2分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

题目来源

282. 给表达式添加运算符(LeetCode)

题目描述(困难

给定一个仅包含数字 0-9 的字符串 num 和一个目标值整数 target ,在 num 的数字之间添加 二元 运算符(不是一元) +- 或 * ,返回 所有 能够得到 target 的表达式。

注意,返回表达式中的操作数 不应该 包含前导零。

示例1

输入: num = "123", target = 6
输出: ["1+2+3", "1*2*3"] 
解释: “1*2*3” 和 “1+2+3” 的值都是6

示例2

输入: num = "232", target = 8
输出: ["2*3+2", "2+3*2"]
解释: “2*3+2” 和 “2+3*2” 的值都是8

示例3

输入: num = "3456237490", target = 9191
输出: []
解释: 表达式 “3456237490” 无法得到 9191

提示

  • 1 <= num.length <= 10
  • num 仅含数字
  • -231 <= target <= 231 - 1

题目解析

方法一:回溯法

回溯法可以理解为决策树的遍历过程,只需考虑三个问题:

  • 路径:已做出的选择
  • 选择列表:可以选择的列表
  • 结束条件:到达底层后的条件

根据题意,设字符串 num 的长度为 n ,为了构建表达式,我们共有 n-1 个空隙可以添加 + 、 - 、 * 或者不添加符号。

我们可以采用回溯法来模拟该过程:从左向右构建表达式,并实时计算表达式结果。其中 路径 便是 数字和符号的组合结果, 选择列表 便是两个数字之间符号的选择情况(是三种符号的任意一种,还是没有符号), 结束条件 便是遍历到最后一个数字。当然此过程要考虑 乘法运算符 的优先级高于 加法运算符 和 减法运算符 ,于是需要考虑连乘时的运算结果存储(如 123)。

定义递归函数backTrack(exp,i,res,mul),其中:

  • exp 为当前构建出的表达式
  • i 表示当前的枚举到了 num 的第 i 个数字
  • res 表示当前表达式的计算结果
  • mul 表示当前表达式最后一个连乘串的运算结果(如 1+2+3,mul便是3;1+2*3,mul便是6)

该递归函数分为两种情况:

  • 如果 i = n ,说明表达式已经构建完成,若此时的 res = target ,说明找到了一个可行解,我们将 exp 放入答案数组,递归结束;
  • 如果 i < n ,需要枚举当前表达式末尾要添加的符号( + 、 - 、 *),以及该符号之后需要截取多少位数字,假设该符号之后的数字为 val,按符号分类讨论:
    • 若添加 + 号,则 res 增加 val , val 单独组成表达式最后一个连乘串
    • 若添加 - 号,则 res 减少 val , - val 单独组成表达式最后一个连乘串
    • 若添加 * 号,由于乘法运算优先级高于加减法运算,则 res 减少 mul ,在增加 valmul , valmul 组成表达式最后一个连乘串

以下是我在失败五次后以及参考官方答案得出的初版代码:

/**
 * @param {string} num
 * @param {number} target
 * @return {string[]}
 */
var addOperators = function (num, target) {
    const n = num.length
    const ant = []    //返回的结果
    const exp = []    //表达式
    var backTrack = (exp, i, res, mul) => {
        if (i === n) {
            if (res === target) {
                ant.push(exp.join(''))
            }
        }

        const index = exp.length
        if (i > 0) {
            exp.push('')
        }
        let val = 0
        for (let j = i; j < n && (j === i || num[i] !== '0'); j++) {
            val = val * 10 + (num[j] - "")
            exp.push(num[j])
            if (i === 0) {
                backTrack(exp, j + 1, val, val)
            } else {
                exp[index] = '+'
                backTrack(exp, j + 1, res + val, val)
                exp[index] = '-'
                backTrack(exp, j + 1, res - val, -val)
                exp[index] = '*'
                backTrack(exp, j + 1, res - mul + val * mul, mul * val)
            }
        }
        exp = exp.splice(index, exp.length - index)
    }
    backTrack(exp, 0, 0, 0)
    return ant
};

如图:

image.png

但是其用时过长,且内存较大并不算是最优解,于是我对其进行了优化,得到了目前的最终版代码:

/**
 * @param {string} num
 * @param {number} target
 * @return {string[]}
 */
var addOperators = function (num, target) {
    const n = num.length
    const ant = []    //返回的结果
    var backTrack = (exp, i, res, mul) => {
        if (i === n) {
            if (res === target) {
                ant.push(exp)
            }
        }
        for (let j = 1; j <= n - i; j++) {
            let str = num.substr(i, j)
            if (j != 1 && str[0] == '0') break
            let val = str - ""
            if (i === 0) {
                backTrack("" + str, i + j, val, val)
            } else {
                backTrack(exp + '+' + str, i + j, res + val, val)
                backTrack(exp + '-' + str, i + j, res - val, -val)
                backTrack(exp + '*' + str, i + j, res - mul + val * mul, mul * val)
            }
        }
    }
    backTrack("", 0, 0, 0)
    return ant
};

如图:

image.png

参考