回溯算法详解

算法思想

回溯实际上是一种试探算法,这种算法跟暴力搜索最大的不同在于,在回溯算法里,是一步一步地小心翼翼地进行向前试探,会对每一步探测到的情况进行评估,如果当前的情况已经无法满足要求,那么就没有必要继续进行下去,也就是说,它可以帮助我们避免走很多的弯路。
回溯算法的特点在于,当出现非法的情况时,算法可以回退到之前的情景,可以是返回一步,有时候甚至可以返回多步,然后再去尝试别的路径和办法。这也就意味着,想要采用回溯算法,就必须保证,每次都有多种尝试的可能。

解题步骤

  1. 判断当前情况是否非法,如果非法就立即返回;

  2. 当前情况是否已经满足递归结束条件,如果是就将当前结果保存起来并返回;

  3. 当前情况下,遍历所有可能出现的情况并进行下一步的尝试;

  4. 递归完毕后,立即回溯,回溯的方法就是取消前一步进行的尝试。

代码模板

function fn(n) {

    // 第一步:判断输入或者状态是否非法?
    if (input/state is invalid) {
        return;
  }

    // 第二步:判读递归是否应当结束?
    if (match condition) {
        return some value;
  }

    // 遍历所有可能出现的情况
    for (all possible cases) {
  
        // 第三步: 尝试下一步的可能性
        solution.push(case)
        // 递归
        result = fn(m)

        // 第四步:回溯到上一步
        solution.pop(case)
    
    }
    
}

例子

Leetcode39:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
说明:

  1. 所有数字(包括 target)都是正整数。
  2. 解集不能包含重复的组合。

画图

假设candidates=[2,3,6,7],target=7,画图如下:
在这里插入图片描述
为了简便,我们只画了2的子节点。遍历顺序是2->2,2->2,2,2->2,2,2,2,返回上一层,接着遍历2,2,2,3->2,2,2,6->2,2,2,7。2,2,2的子节点遍历完,返回到2,2,开始遍历2,2,3->2,2,6->2,2,7。然后返回到2,像这样一直遍历下去即可。遇到满足要求的节点(2,2,3和7)就保存。
你有没有发现这就是深度优先遍历?
代码如下:

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        res=[]
        candidates.sort()
        cur=[]
        def back(start):
            if sum(cur)>target:
                return 
            if sum(cur)==target:
                res.append(cur[:])
                return            
            for i in range(start,len(candidates)):
                cur.append(candidates[i])
                back(i) 
                cur.pop()        
        back(0)
        return res

首先定义res数组来保存最终结果,cur数组表示当前节点。在back函数里,检查cur元素总和是否已经超出了target。如果总和已经超出了target,就立即返回,去尝试其他的数值;如果总和刚好等于target,就把cur添加到结果中,然后返回。
在循环里用start记录当前元素位置,依次添加当前位置之后的元素,递归结束后pop当前元素,返回上一层。

剪枝

观察上面的图可以发现,有些节点是不需要遍历的,因为这是一个有序数组(无序的话可以先排序)。当遍历到2,2,2,2时,2,2,2,2的元素和比7大,2后面的元素都比它大,所以是不用再去遍历2,2,2,3->2,2,2,6->2,2,2,7的。因此就需要剪枝。
在这里插入图片描述
在代码里我们只需要在循环里加一个判断。假如cur元素和加上要遍历的下一个元素的值大于target,就跳出当前循环,返回上一层。

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        res=[]
        candidates.sort()
        cur=[]
        def back(start):
            if sum(cur)>target:
                return 
            if sum(cur)==target:
                res.append(cur[:])
                return            
            for i in range(start,len(candidates)):
                if sum(cur)+candidates[i]>target:
                    break
                cur.append(candidates[i])
                back(i) 
                cur.pop()        
        back(0)
        return res

可以在循环里加一句print(cur)看一下结果。
在这里插入图片描述
而且我们可以发现,加上了这个判断后,我们连2,2,2,2(不满足条件)都不用遍历了。访问2,2,2后可以直接到2,2,3。
你明白了吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值