算法爬坑记——基于组合的深度优先搜索

递归的实质:

如果你写递归时思路渗透到每一层递归的具体情况。那么你的思维方式就落了下乘。

再次探究递归的实质:回头望月

在每个俄罗斯套娃里放一个纸条。

或者是两个人打架,其中一个人说,这次咱俩的事还没完,我先去跟另一个人打一顿,然后这个人跟另一个人说,这次咱俩的事还没玩,我先去跟另一个人打一顿。。。以此类推,直到他跟最后一个人打了一顿,然后返回来和倒数第二个人大家,然后以此类推,和第一个人打架。

采用递归的问题,其目的是做一个循环。但是面临的问题是,循环中有一个回头的过程,在完成这次循环后,需要返回到上一次的循环。所以我需要存储上一次循环所做的事。

因此每层循环(这里先不考虑递归的终点)要做的事有两件:1. 进行本次循环的操作。2. 进入下一层循环。

本次循环的操作就是你要做什么。由于递归是一个循环,所以每一层做的事都是一样的。所以这些一样的事就是本次操作。

进入下一层循环就是直接调用自身这个函数。进入下一层循环中往往只是改变某些参数,这个参数影响着递归的出口。而一层递归之后会返回上一层递归。如果是遍历的话,返回值通常是void,如果是分治的话,返回值通常不是void。

写递归的时候,首先考虑这种循环到什么时候停止,在停止的时候需要做什么样的操作然后再return。在写本层要干的事的时候,关键问题在什么时候进入下一层递归,什么时候干事。这时候把每次递归看作是已经结束递归并返回。

总结一下,使用递归面临的两个问题:1.用不用2.怎么用。

对于为什么要用递归这个问题,其中的大魔王是:这次咱俩的事还没完,等会我接着干你。当需要回头的时候,就用递归。

对于如何写递归这个问题,大魔王是:什么时候进入下一层循环。

Subsets:

注意,每层在递归完成之后,要把最后一个数删掉。

这个题有两种思路,但是都用到了递归。一种是简单DFS,一种是能够拓展到排序的DFS。凡是需要回头的,都可以用递归来做,如果不让用递归,就用一个stack来模拟递归的情况。递归实际上就是操作系统自动为你进行了一个stack操作。这个stack中存储的是这层函数的所有变量。所以每层递归的index都是不一样的。如果一层递归返回到上一层之后,这层递归中的index会消失。函数采用的index是本层的index,而不是返回函数的index。


简单DFS:对于nums中的每一个元素,都可以采取两种操作:选择这个元素,或者不选这个元素。对于每种情况进行一个讨论。需要注意的是,每层递归的有一个参数是不一样的。递归所改变的参数是判断递归出口的依据。

注意,里面有一个深度拷贝 new ArrayList<Integer>(subset) 。因为这个函数是不返回值的,所以subset是在每层递归之间不断传递的。DFS可以认为是一个不知道有多少层的多层循环。很多搜索问题都可以按照这种树的方式去写。

用排列的方式去写。加startIndex的目的是为了保证是一个从小到大的顺序。保证一个数不会被选第二次。这种做法是比较通用的。对于这种情况,每一层的操作不仅包括进入下一层,还包括把最后一个节点去除的过程。

这个代码中,实现回头的方法是通过for循环。这个代码的本质思想是不断挑出以某个数开头的所有情况。


Subsets II:考点在去重,去重是在找排列的时候就应该完成去重的过程。普通 搜索避免重复的过程,通过排序,然后用index来避免。 

去重的步骤:发现哪些步是重复的; 选出一个代表; 先排序,然后搞一波。如果限定重复次数:现在candidates里面去重

什么时候使用DFS:1. 碰到让你找所有方案的题,基本都是用DFS,但是会耗费stack空间。(有些题也可以用BFS来做。但是缺点是耗费堆空间???每层都会耗费很多的空间,而且每层都要记录在内。)。2. 除了二叉树以外90%的题,要么是排列,要么是组合。 排列组合的问题,最好也用BFS练一下。

Combination Sum:找出满足所有条件的组合,或者找出所有方案中最优/最大的组合。组合的变化是指数级别的,所以算法就是搜索。注意这个题是可以包含重复的。所以一种解决的方法是,在最开始就进行去重(用双指针去重);第二种是直接跳过重复的数,不去取重复的数。当target取0的时候,才会将结果记录下来。

Combination Sum:几乎没区别。

K数Sum:所有求出sum的问题实质上都是一样的

 Partition Palindrome:凡是要求出所有方案的问题,都是搜索。动态规划只能解决其中一个小小的步骤。动态规划解决的只是方案的个数,或者能不能存在这种方案。将分割类的问题看作是对 空隙 的选择。回文串的预处理!!!!!!!!!

排列组合其实也是有模板的。

DFS 时间复杂度的通用计算公式:O(答案个数 * 构造每个答案的时间)

通配符匹配/正则表达式匹配:可用动态规划,或者是DFS。

正则表达式的匹配问题一定都是阉割过的,不会太麻烦。正则表达式的递归方法和Subset中决策法是很像的。匹配的过程中关键在于 * 的讨论。

通俗的理解DFS,就是一条路走到死,然后再拐回来把另一条路也走到死。

Split String:

Word BreakII:和回文串分割类似。切割类问题,都可以认为是排序搜索。采用记忆化搜索的方法来解决。代码可读性:不应该缩进太多。时间复杂度等于O(答案个数 * 构造答案的时间) 。最坏情况是 n × 2 ^n。因为构造字符串也需要时间。


猜你喜欢

转载自blog.csdn.net/chichiply/article/details/80628702
今日推荐