家装设计需要学什么软件,seo工具助力集群式网站升级,郑州今天的最新消息,网站后台上传案例能同步到博客吗语言#xff1a;Java/Go
回溯理论基础 回溯函数也就是递归函数#xff1b; 所有回溯法的问题都可以抽象为树形结构#xff1b; 回溯法解决的都是在集合中递归查找子集#xff0c;集合的大小就构成了树的宽度#xff0c;递归的深度#xff0c;都构成的树的深度。 适用的题…语言Java/Go
回溯理论基础 回溯函数也就是递归函数 所有回溯法的问题都可以抽象为树形结构 回溯法解决的都是在集合中递归查找子集集合的大小就构成了树的宽度递归的深度都构成的树的深度。 适用的题目类型
组合问题N个数里面按一定规则找出k个数的集合切割问题一个字符串按一定规则有几种切割方式子集问题一个N个数的集合里有多少符合条件的子集排列问题N个数按一定规则全排列有几种排列方式棋盘问题N皇后解数独等等
组合无序排列有序
回溯法解题模板
回溯函数返回值及参数回溯算法中函数返回值一般为void。需要什么参数就填什么参数 void backtracking(参数)
回溯函数终止条件一般搜到叶子节点了也就找到了满足条件的一条答案把这个答案存放起来并结束本层递归。 if (终止条件) {存放结果;return;
}
回溯搜索的遍历过程 上图中集合大小和孩子的数量是相等的for循环就是遍历集合区间可以理解一个节点有多少个孩子这个for循环就执行多少次。for循环可以理解是横向遍历backtracking递归就是纵向遍历这样就把这棵树全遍历完了一般来说搜索叶子节点就是找的其中一个结果了 for (选择本层集合中元素树中节点孩子的数量就是集合的大小) {处理节点;backtracking(路径选择列表); // 递归回溯撤销处理结果
}
因此回溯算法模板框架如下
void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择本层集合中元素树中节点孩子的数量就是集合的大小) {处理节点;backtracking(路径选择列表); // 递归回溯撤销处理结果}
}组合 给定两个整数 n 和 k返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1 输入n 4, k 2
输出
[[2,4],[3,4],[2,3],[1,2],[1,3],[1,4],
] 组合的题目如果单纯的采用for循环暴力求解k越大for嵌套层数就越多很崩溃。因此采用回溯算法当然回溯算法本身也是暴力求解但胜在书写方便。其原理是用递归来解决嵌套层数的问题。每一次的递归中嵌套一个for循环就可以用于解决多层嵌套循环的问题了。
因此把上面的组合问题抽象为如下树形结构 这棵树一开始集合是 1234 从左向右取数取过的数不再重复取。第一次取1集合变为234 因为k为2我们只需要再取一个数就可以了分别取234得到集合[1,2] [1,3] [1,4]以此类推。n相当于树的宽度k相当于树的深度图中每次搜索到了叶子节点我们就找到了一个结果。
因此首先确定回溯三部曲
递归函数的返回值以及参数首先要定义两个全局变量一个用来存放符合条件单一结果一个用来存放符合条件结果的集合函数里有n和k两个int型的参数还需要一个参数为int型变量startIdx这个参数用来记录本层递归的中集合从哪里开始遍历。startIdx 是防止出现重复的组合。在集合[1,2,3,4]取1之后下一层递归就要在[2,3,4]中取数了那么下一层递归如何知道从[2,3,4]中取数呢靠的就是startIdx。用startIdx来记录下一层递归搜索的起始位置。
回溯函数终止条件path这个数组的大小如果达到k说明我们找到了一个子集大小为k的组合了在图中path存的就是根节点到叶子节点的路径。此时用result二维数组把path保存起来并终止本层递归。单层搜索的过程for循环每次从startIndex开始遍历然后用path保存取到的节点ibacktracking递归函数通过不断调用自己一直往深处遍历总会遇到叶子节点遇到了叶子节点就要返回。然后进行回溯撤销本次处理的结果便于进行下一次递归。
剪枝优化
如果n 4k 4的话那么第一层for循环的时候从元素2开始的遍历都没有意义了。 在第二层for循环从元素3开始的遍历都没有意义了。 图中每一个节点图中为矩形就代表本层的一个for循环那么每一层的for循环从第二个数开始遍历的话都没有意义都是无效遍历。
所以可以剪枝的地方就在递归中每一层的for循环所选择的起始位置。
如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了那么就没有必要搜索了。
看一下优化过程如下 已经选择的元素个数path.size(); 还需要的元素个数为: k - path.size(); 在集合n中至多要从该起始位置 : n - (k - path.size()) 1开始遍历
为什么有个1呢因为包括起始位置我们要是一个左闭的集合。
举个例子n 4k 3 目前已经选取的元素为0path.size为0n - (k - 0) 1 即 4 - ( 3 - 0) 1 2。
从2开始搜索都是合理的可以是组合[2, 3, 4]。
所以优化之后的for循环是
for (int i startIndex; i n - (k - path.size()) 1; i) // i为本次搜索的起始位置class Solution {ListListInteger res new ArrayList();LinkedListInteger path new LinkedList();public void backTracking(int n, int k, int startIdx){//startIdx标志从哪开始循环if(path.size()k){res.add(new ArrayList(path));return;}for(int istartIdx;in-(k-path.size())1;i){//剪枝path.add(i);backTracking(n,k,i1); //递归path.removeLast();}} public ListListInteger combine(int n, int k) {backTracking(n,k,1);return res;}
}
今日心得
开启回溯章节在这一章要多熟悉回溯三部曲以及捋清楚回溯的内在逻辑。并且注意数组、列表和树的操作。