🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇
⭐回溯⭐
🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇
前言
回溯算法是⼀种经典的递归算法,通常⽤于解决组合问题、排列问题和搜索问题等。
回溯算法的基本思想:从⼀个初始状态开始,按照⼀定的规则向前搜索,当搜索到某个状态⽆法前进 时,回退到前⼀个状态,再按照其他的规则搜索。
回溯算法在搜索过程中维护⼀个状态树,通过遍历 状态树来实现对所有可能解的搜索。 回溯算法的核⼼思想:“试错”,即在搜索过程中不断地做出选择,如果选择正确,则继续向前搜 索;否则,回退到上⼀个状态,重新做出选择。
回溯算法通常⽤于解决具有多个解,且每个解都需要 搜索才能找到的问题。
回溯算法的模板
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Backtracking {
public void backtracking(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(candidates);
// 如果需要对candidates进行排序
backtrack(result, new ArrayList<>(), candidates, target, 0);
}
private void backtrack(List<List<Integer>> result, List<Integer> tempList, int[] candidates, int remain, int start) {
if (remain < 0) {
// 如果 remain 小于 0,表示当前的组合不符合要求,直接返回
return;
} else if (remain == 0) {
// 如果 remain 等于 0,表示当前的组合符合要求,加入到结果中
result.add(new ArrayList<>(tempList));
} else {
for (int i = start; i < candidates.length; i++) {
if (i > start && candidates[i] == candidates[i - 1]) {
continue;
// 避免重复计算,如果当前数字和上一个数字相同,则跳过
}
tempList.add(candidates[i]); // 将当前数字加入到临时列表中
backtrack(result, tempList, candidates, remain - candidates[i], i + 1);
// 递归调用,继续向下搜索
tempList.remove(tempList.size() - 1);
// 回溯,将最后加入的数字移除,尝试下一个数字
}
}
}
}
回溯算法是⼀种⾮常重要的算法,可以解决许多组合问题、排列问题和搜索问题等。回溯算法的核⼼ 思想是搜索状态树,通过遍历状态树来实现对所有可能解的搜索。回溯算法的模板⾮常简单,但是实 现起来需要注意⼀些细节,⽐如如何做出选择、如何撤销选择等。
2. 目标和(medium)
问题描述
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
算法思路
算法思路
定义状态:
index
:当前处理到数组nums
的索引。path
:当前运算路径上的累加结果。递归终止条件:
- 当
index
达到数组nums
的长度时,判断path
是否等于target
。如果等于target
,则找到了一个有效的表达式,计数器ret
加 1。递归过程:
- 对于每一个
index
,有两种选择:在当前数字前加上 '+' 或 '-'。- 对于每种选择,递归调用自身,处理下一个索引
index + 1
。- 在选择 '+' 时,
path
增加当前的nums[index]
。- 在选择 '-' 时,
path
减去当前的nums[index]
。回溯:
- 在
dfs2
方法中,使用全局变量path
来存储当前的累加结果。每次递归调用前后需要更新path
,并在返回时恢复原状,以便不影响其他分支的计算。代码解释
dfs1 方法
dfs1
方法采用传参的方式传递当前路径的累加结果path
。- 每次递归调用时,分别尝试加法和减法,并递归处理下一个索引。
- 当到达数组末尾时,检查当前路径的结果是否等于目标值
target
。dfs2 方法
dfs2
方法使用全局变量path
来存储当前路径的累加结果。- 在递归调用前后,需要对
path
进行更新和恢复,以实现回溯。- 每次递归调用时,分别尝试加法和减法,并递归处理下一个索引。
- 当到达数组末尾时,检查当前路径的结果是否等于目标值
target
。
代码实现:
class Solution {
//path指的是运算到当前数字的结果,这里目标结果是整形,非数组,把 path 放到参数里代码更加简洁
int ret;
public int findTargetSumWays(int[] nums, int target) {
ret =0;
int path =0;
// dfs1(nums,target,path,0);
dfs2(nums,target,0);
return ret;
}
//dfs path做参数
public void dfs1(int[] nums,int target,int path,int index){
if(index==nums.length){
if(path == target){
ret++;
}
return;
}
//选加法
dfs1(nums,target,path+nums[index],index+1);
//选减法
dfs1(nums,target,path-nums[index],index+1);
}
int path =0;
//dfs2 path做全局变量 所以在运算的时候要回溯
public void dfs2(int[] nums,int target,int index){
if(index==nums.length){
if(path == target){
ret++;
}
return;
}
//选加法
path+=nums[index];
dfs2(nums,target,index+1);
//回溯
path-=nums[index];
//选减法
path-=nums[index];
dfs2(nums,target,index+1);
//回溯
path+=nums[index];
}
}
代码优化:
问题背景
给定一个非负整数数组 nums
和一个整数 target
,向数组中的每个整数前添加 '+' 或 '-',然后串联起所有整数,构造一个表达式。返回可以通过上述方法构造的、运算结果等于 target
的不同表达式的数量。
解析
-
数学分析:
- 设最终选取的结果中,前面加
+
号的数字之和为a
,前面加-
号的数字之和为b
。 - 整个数组的总和为
sum
,于是我们有:a + b = sum
a - b = target
- 消去
b
可得a = (sum + target) / 2
。
- 设最终选取的结果中,前面加
-
转化为背包问题:
- 我们只需要在
nums
数组中选择一些数,使得它们的和为(sum + target) / 2
。 - 这个问题可以转化为经典的背包问题:在给定的数组中选出一些数,使得它们的和等于某个目标值。
- 我们只需要在
动态规划算法
状态表示:
dp[i][j]
表示在前i
个数中选,总和正好等于j
的方案数。状态转移方程:
- 不选择
nums[i]
:dp[i][j] = dp[i - 1][j]
- 选择
nums[i]
:如果nums[i - 1] <= j
,则dp[i][j] += dp[i - 1][j - nums[i - 1]]
初始化:
dp[0][0] = 1
,表示不选择任何元素时,总和为 0 的方案数为 1。返回值:
- 返回
dp[n][aim]
的值,其中n
为数组大小,aim
为要凑的目标和。空间优化:
- 由于
dp
表的第二维只依赖于前一行的数据,可以将二维数组简化为一维数组。- 修改第二层循环的遍历顺序,以避免覆盖所需的数据。
public int findTargetSumWays(int[] nums, int target) {
int sum=0;
//计算sum 和
for(int x:nums){
sum+=x;
}
int aim = (sum +target);
//这里是 2a = sum + target, 如果差的结果不为偶数就说明不存在这种表达式
if(aim%2!=0 || aim<0){
return 0;
}
aim/=2;
//建表 dp[i][aim] 表示的是挑选前 i 个数中某些数的和为 aim 的挑选方式的个数
int[][] dp = new int[nums.length+1][aim+1];
//初始化 在前 0 个数中挑选的和只能为 0 这一种,其他和都不存在所以
dp[0][0] = 1;
//填表
for(int i = 1;i<nums.length+1;i++){
for(int j = 0;j<aim+1;j++){
dp[i][j] = dp[i-1][j];
if(j>=nums[i-1]){ //这里 i-1 是因为当时建表的时候多建了一层
dp[i][j]+=dp[i-1][j-nums[i-1]];
}
}
}
return dp[nums.length][aim];
}
总结
动动手点个赞会让作者更开心,感谢阅览,加油各位 !