前言
回溯算法是一种试探性的算法,它在解决问题时测验各种或许的解,并经过在每一步挑选中进行回退(backtrack)来找到问题的解。在JavaScript中,回溯算法通常用于解决一些组合、摆放、子集和查找等问题。
回溯算法的基本思想:
- 挑选: 在问题的每一步,算法测验各种或许的挑选。
- 判断: 对每个挑选进行评价,确认是否满意问题的条件。
- 回退: 假如挑选导致无解或不满意条件,算法回退到之前的状况,测验其他挑选。
下面我将用LeetCode中的例题进行解说。
1. 全摆放问题(Permutations)
问题描绘: 给定一个不含重复数字的数组 nums,回来其一切或许的全摆放。
var permute = function(nums) {
const result = [];
function backtrack(path, used) {
// 停止条件:当当时途径的长度等于数组长度时,阐明找到一个全摆放
if (path.length === nums.length) {
result.push([...path]); // 将当时途径拷贝一份参加成果集
return;
}
// 遍历数组元素
for (let i = 0; i < nums.length; i ) {
// 越过现已运用过的元素
if (used[i]) continue;
// 做出挑选:将当时元素参加途径,并符号为已运用
path.push(nums[i]);
used[i] = true;
// 递归调用,持续往下挑选
backtrack(path, used);
// 吊销挑选:回溯到上一层状况
path.pop();
used[i] = false;
}
}
// 初始调用回溯函数
backtrack([], []);
return result;
};
在上述代码中,我们构建了一个backtrack
的回调函数,它承受两个参数,path
表明当时的途径,used
用于符号数组元素是否被运用过。并参加了停止条件:当当时途径的长度等于数组长度时,阐明找到一个全摆放。然后做出挑选。以此经过递归的方式,运用回溯算法在不同的挑选途径上查找全摆放,保证每个元素都在每个方位上都有机会出现。
2. 子集问题(Subsets)
问题描绘: 给定一个不含重复元素的整数数组 nums,回来该数组一切或许的子集。
var subsets = function(nums) {
const result = [];
function backtrack(start, path) {
// 将当时途径参加成果集
result.push([...path]);
// 遍历数组元素
for (let i = start; i < nums.length; i ) {
// 做出挑选:将当时元素参加途径
path.push(nums[i]);
// 递归调用:持续往下挑选,从下一个元素开端
backtrack(i 1, path);
// 吊销挑选:回溯到上一层状况
path.pop();
}
}
// 初始调用回溯函数,从第一个元素开端,初始途径为空
backtrack(0, []);
// 回来终究的成果集
return result;
};
现在逐步解说这段代码的运转进程:
-
初始调用: 最初调用
backtrack(0, [])
,表明从数组的第一个元素开端挑选,当时途径为空。 -
遍历数组元素: 进入
for
循环,从start
开端遍历数组元素。 -
做出挑选: 将当时元素参加途径
path
,这里是nums[i]
。 -
递归调用: 调用
backtrack(i 1, path)
,持续往下挑选,下一层的起始方位为i 1
,表明从当时元素的下一个元素开端挑选。 - 遍历和递归: 重复上述过程,遍历数组元素,做出挑选,递归调用,直到遍历完结。
-
吊销挑选: 当递归回来后,回到上一层状况,吊销当时挑选,即
path.pop()
,这样就回溯到上一层状况。 -
将当时途径参加成果集: 在每一层递归回来后,都将当时途径参加成果集,即
result.push([...path])
。 - 重复进程: 重复以上过程,直到遍历完一切或许的挑选,得到一切子集。
3. 组合求和问题(Combination Sum)
问题描绘: 给定一个无重复元素的数组 candidates 和一个方针数 target,找出 candidates 中一切可以使数字和为 target 的组合。
var combinationSum = function(candidates, target) {
const result = [];
function backtrack(start, target, path) {
// 停止条件:当方针值为0时,表明找到一个组合,将当时途径参加成果集
if (target === 0) {
result.push([...path]);
return;
}
// 遍历数组元素
for (let i = start; i < candidates.length; i ) {
// 越过不符合条件的元素
if (target - candidates[i] < 0) continue;
// 做出挑选:将当时元素参加途径
path.push(candidates[i]);
// 递归调用:持续往下挑选,更新方针值,起始方位不变
backtrack(i, target - candidates[i], path);
// 吊销挑选:回溯到上一层状况
path.pop();
}
}
// 初始调用回溯函数,从数组的第一个元素开端,方针值为target,初始途径为空
backtrack(0, target, []);
// 回来终究的成果集
return result;
};
下面逐步解说代码的运转进程:
-
初始调用: 调用
backtrack(0, target, [])
,表明从数组的第一个元素开端挑选,方针值为target
,初始途径为空。 -
停止条件: 在
backtrack
函数内部,首先检查停止条件,当target
等于 0 时,表明找到一个组合,将当时途径path
参加成果集result
。 -
遍历数组元素: 运用循环遍历数组元素,从
start
开端。 -
越过不符合条件的元素: 假如
target - candidates[i] < 0
,阐明当时元素参加途径后方针值将小于0,越过当时元素。 -
做出挑选: 将当时元素参加途径
path
。 -
递归调用: 调用
backtrack(i, target - candidates[i], path)
,持续往下挑选,更新方针值为target - candidates[i]
,起始方位不变。 - 吊销挑选: 在递归回来后,回溯到上一层状况,将当时元素从途径中弹出。
- 重复进程: 重复以上过程,直到遍历完一切或许的挑选,得到一切满意条件的组合。
总结
- 在每个问题中,都界说了一个
backtrack
函数,负责履行回溯算法的中心逻辑。 -
path
参数用于保存当时的途径,表明一种或许的解。 - 关于全摆放和子集问题,经过循环遍历数组元素,做出挑选并递归,然后吊销挑选。
- 关于组合求和问题,同样经过循环遍历数组元素,但在递归时传递的
target
值会递减,直到target
为0时,表明找到一个解。
注意事项:
- 回溯算法通常运用递归实现。
- 在每一步的挑选中,需要进行挑选、递归、回退三个过程。
- 需要谨慎处理状况的保存和吊销,保证不影响算法的正确性。