回溯算法基础

概念

回溯算法(Backtracking Algorithm)是一种用于解决在搜索问题中找到所有可能解的常用算法。回溯算法的核心思想是逐步构建问题的解决方案,并在发现当前解决方案无法满足问题要求时,回溯到上一个步骤,尝试不同的选择,直到找到所有可能的解或确定无解。这一过程可以看作是在一个决策树或状态树上的深度优先搜索。

回溯法可以理解为通过选择不同的岔路口寻找目的地,一个岔路口一个岔路口的去尝试找到目的地。如果走错了路,继续返回来找到岔路口的另一条路,直到找到目的地。

回溯算法通常涉及以下步骤

  1. 选择一个候选解的初始状态。
  2. 逐步构建解决方案,每一步都尝试一个可能的选择。
  3. 检查当前选择是否满足问题的要求。如果满足,继续下一步;如果不满足,回溯到上一个步骤,尝试其他选择。
  4. 重复步骤2和步骤3,直到找到一个解决方案或确定没有更多的选择。

回溯法解决的问题

回溯法,一般可以解决如下几种问题:

回溯算法模版

摘录自代码随想录-回溯法模版
回溯函数伪代码如下:

def backtracking(路径,选择列表):
if 终止条件:
	存放结果
	return 

attach/d6a307889e6aa2557443f07534c60a0f_MD5.png

注意图中,我特意举例集合大小和孩子的数量是相等的!
回溯函数遍历过程伪代码如下:

for 选择 in 本层选择列表:
	处理节点;
    backtracking(路径,选择列表); // 递归
    回溯,撤销处理结果

for循环就是遍历集合区间,可以理解一个节点有多少个孩子,这个for循环就执行多少次。
backtracking这里自己调用自己,实现递归。
大家可以从图中看出for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。
分析完过程,回溯算法模板框架如下:

def backtracking(路径,选择列表):
	if 终止条件:
		存放结果
		return   
	for 选择 in 本层选择列表:
		做选择
	    backtracking(路径,选择列表) # 递归
	    回溯,撤销选择

例题

组合

77. 组合 - 力扣(LeetCode)
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
输入: n = 4, k = 2
输出:

[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

示例 2:

输入: n = 1, k = 1
输出: [[1]]
提示:

回溯代码

class Solution:
    def __init__(self):
        self.tmp = []
        self.res =[]
    def combine(self, n: int, k: int) -> List[List[int]]:
        self.getlist(n,1,k)
        return self.res
    # 回溯函数
    def getlist(self, n, index, k):
        if len(self.tmp)==k:
            self.res.append(self.tmp[:]) # 存放结果,注意这里必须为tmp[:],不然tmp内容改变后res中的内容也会随着改变
            return
        
        for i in range(index,n+1):
            self.tmp.append(i)     # 处理节点
            self.getlist(n,i+1,k)  # 递归函数
            self.tmp.pop()         # 回溯,撤销处理结果

组合总和2

40. 组合总和 II - 力扣(LeetCode)

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

**注意:**解集不能包含重复的组合。

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:

[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
输出:

[
[1,2,2],
[5]
]

提示:

解题

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        candidates = sorted(candidates)
        res =[]
        self.getNum(candidates,target,0,[],res)
        return res

    def getNum(self,candidates,target,start,tmp,res):
        # print(start)
        if target<0:
            return
        if target==0:
            res.append(tmp[:])
            return
        for i in range(start,len(candidates)):
            # 如果当前值大于目标值,便不再继续
            if candidates[i]>target:
                break
            # 如果有重复数字,那么就跳过,注意,是从start位置开始判断
            if i>start and candidates[i]==candidates[i-1]:
                continue
            tmp.append(candidates[i])
            # 每次让 target-当前值
            self.getNum(candidates,target-candidates[i],i+1,tmp,res) # 递归算法
            tmp.pop()                                                # 回溯