文章摘要:
- 本文解析了LeetCode上的目标和问题,要求在非负数组的每个数字前添加"+"或"-",使得总和等于目标值。通过决策树和深度优先搜索(DFS)实现组合枚举,分别讨论了全局变量和函数参数两种回溯方法。核心思路是递归遍历所有可能的符号组合,统计符合条件的路径数。代码示例展示了两种实现方式,并解释了回溯与剪枝的细节。最终,该问题转化为二叉树路径搜索,时间复杂度为指数级,但通过回溯优化避免了重复计算。
文章目录
- 一、题目解析
- [二、算法原理 + 代码实现](#二、算法原理 + 代码实现)
一、题目解析

题目给我们一个非负数组,要我们对数组中的每个数字添加 "加号+ " 或 "减号- ",使得数组中的数字之和等于 target。要我们返回能使得数组中数字之和等于 target 的组合个数。
例如,nums = [ 1, 1, 1, 1, 1 ],target = 3。我们需要对 nums 数组中的每一个数字添加符号 "+" 或 "-" ,使得 nums 中的数字之和等于 3。能够满足的组合:+1+1+1+1-1 = 3;+1+1+1-1+1 = 3;+1+1-1+1+1 = 3;+1-1+1+1+1 = 3;-1+1+1+1+1 = 3。因此共有五种,返回 5。

二、算法原理 + 代码实现
本题同样是一道找组合的题目,按照暴搜的思考方式来做。
决策树
我们根据示例1,画出决策树。

遍历 nums 数组,然后依次选择给当前数字添加 "+" 或者 "-",看看总共有多少种组合。
我们在每一个节点直接写上数组中数字之和,则决策树(部分)如下:

- 从第一个数字开始,若选择给它添加 "+",则得到 1;若选择给它添加 "-",则得到 -1。
- 基于第一个数字为 "1" 的情况,再给第二个数字选择符号,若是 "+",得到 1,两数之和就是 2;若是 "-",得到 -1,两数之和就是 0。
- 基于第一个数字为 "1" 、第二个数字为 "1" 的情况,再给第三个数字选择...以此类推。
- 最终在叶子节点就可以得到结果,统计结果的数量为 5。
全局变量
这里需要两个全局变量:ret 记录结果,path 记录路径。本题可以多设置一个 aim,作为 target,就无需每一次递归都将 target 作为参数传来传去了。
Java
int ret, path, aim;
dfs 函数
函数头
我们在每一次的递归过程中都需要对 nums 数组中的数字进行添加符号,因此需要将 nums 作为参数。由于我们是通过递归对 nums 数组进行遍历的,因此需要知道每一次递归的时候遍历的位置 pos,需要将 pos 下标作为参数。
Java
dfs(int[] nums, int pos);
本题有另一种思路是将记录路径的变量 path 改为函数参数,具体原因是:本题的路径可以依靠程序自动完成恢复现场的操作(只是对整数类型进行加减),回到上一层就相当于完成了恢复现场的执行了(读者可以自行模拟一遍流程体会),因此可以将它作为参数(参考 二叉树的所有路径 这道题目)。而作为全局变量是因为依靠程序的回溯无法自动完成恢复现场,需要在 dfs 函数中手动处理来恢复现场(例如数组,没法通过函数参数的方式自动完成恢复现场的操作,只能手动处理),干脆改成全局变量(参考 子集 这道题目)。
Java
dfs(int[] nums, int pos, int path);
函数体
我们每一次递归要做的事情就是选择 "+" 或者 "-",分别执行这两种情况的逻辑就行了。
Java
// path 作为全局变量
// 添加加号的情况
path += 当前位置的数字;
// 递归
// 回溯恢复现场
// 添加减号的情况
path -= 当前位置的数字;
// 递归
// 回溯恢复现场
若是 path 作为函数参数,则代码会相对简洁,因为无需手动恢复现场
Java
// 添加加号的情况
dfs(nums, 下一个数字的下标, path + 当前位置的数字);
// 添加减号的情况
dfs(nums, 下一个数字的下标, path - 当前位置的数字);
细节问题
回溯
path 为全局变量的时候,需要在回溯的时候恢复现场,将加上/减去的数字再减去/加上即可。
而当 path 为函数参数的时候,程序执行回溯会自动恢复现场,无需手动处理。
剪枝
我们这里是将所有组合都枚举到了,因此不涉及到剪枝操作。
递归出口
在给当前数字添加符号之前我们要判断一下 pos 是否到达数组末尾,如果到了数组末尾(即叶子节点),再判断一下当前的 path 是否等于 aim/target,如果等于就更新结果然后返回,否则不更新结果直接返回。
代码实现
path 作为全局变量的版本
Java
class Solution {
int ret, aim, path;
public int findTargetSumWays(int[] nums, int target) {
aim = target;
dfs(nums, 0);
return ret;
}
private void dfs(int[] nums, int pos) {
// 递归出口
if (pos == nums.length) {
if (path == aim) ret++;
return;
}
// 添加加号的情况
path += nums[pos];
dfs(nums, pos + 1);
path -= nums[pos]; // 回溯恢复现场
// 添加减号的情况
path -= nums[pos];
dfs(nums, pos + 1);
path += nums[pos]; // 回溯恢复现场
}
}
path 作为函数参数的版本
Java
class Solution {
int ret, aim;
public int findTargetSumWays(int[] nums, int target) {
aim = target;
dfs(nums, 0, 0);
return ret;
}
private void dfs(int[] nums, int pos, int path) {
// 递归出口
if (pos == nums.length) {
if (path == aim) ret++;
return;
}
// 添加加号的情况
dfs(nums, pos + 1, path + nums[pos]);
// 添加减号的情况
dfs(nums, pos + 1, path - nums[pos]);
}
}
文章到这里就告一段落啦,若有错误请尽管指出~
完