回溯法学习

323 阅读2分钟

回溯法

理论

回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。 ---引用自百度百科

回溯法与递归的关系十分密切,可以说只要有递归就会有回溯

当我们使用回溯法解决问题时,采用的回溯函数就是递归函数

效率

回溯法的性能不是很高,他其实就是一种纯暴力搜索,我们搜索出来所有的可能,然后选出我们需要的结果。

可以解决的问题

  • 组合问题
  • 切割问题
  • 子集问题
  • 排列问题
  • 棋盘问题

理解回溯法

我们可以把整个回溯法的过程看做对一个多叉树的遍历

image.png

回溯法模板

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

例题

以一道力扣题来演示回溯法解题的步骤 [力扣77题](77. 组合 - 力扣(LeetCode) (leetcode-cn.com))

给定两个整数 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]]

根据题条件我们可以构造出这样一棵多叉树:

image.png 从这个多叉树来看,每个叶子结点就是我们需要的结果

然后就是代码的实现

class Solution {
    //存放一次的结果
    LinkedList<Integer> list1 = new LinkedList<>();   
    //最后需要的结果集
    List<List<Integer>> list2 = new ArrayList<>();  
    public List<List<Integer>> combine(int n, int k) {
        combine1(n,k,1);
        return list2;
    }
    public void combine1(int n,int k,int start){
        if(list1.size() == k){
            // 当list1中的元素个数为k的时候说明得到了一次结果
            list2.add(new ArrayList<>(list1));
            return;
        }
        // 遍历集合
        for(int i = start;i <= n;i++){
            // 处理节点
            list1.add(i);
            combine1(n,k,i+1);
            // 回溯操作
            list1.removeLast();
        }  
    }
}

剪枝操作:

举个例子 当 n = 4 ,k =3时,起点从3开始就无效了,如图

image.png

如何实现剪枝操作呢?

for(int i = start;i <= n;i++){
        // 处理节点
        list1.add(i);
        combine1(n,k,i+1);
        // 回溯操作
        list1.removeLast();
    }  

可以把上面这部分改成

   for(int i = start;i <= n - (k-list1.size())+1;i++){
            list1.add(i);
            combine1(n,k,i+1);
            list1.removeLast();
        }  

其中

list1.size() 是当前已选择了的节点的个数

k-list1.size() 是仍需选择的节点的个数

n 是该集合的长度

n - (k-list1.size())+1 代表在集合n中至多从起始位置n - (k-list1.size())+1开始遍历

当 list1.size() = 1 时 k-list1.size() = 2 从2开始搜索都是正确的 剪掉 3 4

当 list1.size() = 2 时 k-list1.size() = 1 从3开始搜索都是正确的 剪掉 4

也就是

image.png

参考:代码随想录 力扣题解