我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情
题目来源
题目描述(困难)
给定一个仅包含数字 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 <= 10num仅含数字-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 组成表达式最后一个连乘串
- 若添加 + 号,则 res 增加 val ,
以下是我在失败五次后以及参考官方答案得出的初版代码:
/**
* @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
};
如图:
但是其用时过长,且内存较大并不算是最优解,于是我对其进行了优化,得到了目前的最终版代码:
/**
* @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
};
如图: