递归+回溯+DFS+BFS

计算递归的空间复杂度时,一般都从栈的角度出发,因为每调用一次函数就会占用栈的一个格子,看调用了几次可以看出空间复杂度是多少

递归的核心思想:把规模大的问题转化为规模小的且逻辑相似的子问题来解决

递归三要素:明白这个递归function的作用是什么;递归终止条件(以及终止之后的处理方法);递归body的逻辑(抽象出一个干净利落的重复逻辑用到子问题以及最终母问题上)

:将大问题化为小问题,直到无法再细化为止,去解决这个小问题;先写递归body再进行递归操作实在“递”去的过程中解决问题

:将解决了的小问题一层一层往回返,解决原问题;先进行递归操作再写递归body是在“归”来的过程中解决问题

递归的常见应用场景:

(1). 问题的定义是按递归定义的(Fibonacci函数,阶乘,…);

(2). 问题的解法是递归的(有些问题只能使用递归方法来解决,例如,汉诺塔问题,…);

(3). 数据结构是递归的(链表、树等的操作,包括树的遍历,树的深度,…)。

递归的时间效率和空间效率一般不高

千万不要模拟铺开递归的过程和细节,只需搞清母问题与子问题是怎么联系起来的,中间有何关系,以及抓住函数的输入和输出应该是什么即可;一个巧妙的方法:在想递归body的逻辑应该怎么写时,先明白输入和输出是什么,然后假设你在函数body里使用递归时就是可以得到想要的结果,并且若有return,还可以使用return的结果去完成中间逻辑的编写。

递归的弊端

  1. 堆栈溢出:递归的本质是函数调用,而函数调用过程中会用栈来保存临时变量,如果递归很多很多层,一直压入栈,可能栈就会满导致后面的操作无法入栈,造成栈溢出
  2. 重复计算:为避免重复计算,优化方案是,可以使用一个数据结构来保存已经求解过的       f(k)值,当递归到k时,先判断f(k)是否已求解过,可避免一些重复运算。

把递归改为非递归时,一般都是用模拟栈的方法

常见的递归算法:归并,快排,二叉树高度,斐波那契,阶乘,全排列(把n个数据的所有排列情况都找出来)


回溯法是一种纯暴力搜索,或者说是按照深度优先的顺序穷举;

判断回溯很简单,拿到一个问题,你感觉如果不穷举一下就没法知道答案,那就可以开始回溯了

回溯是递归的副产品,只要有递归就一定会有回溯

最基本的回溯算法就是在解空间内穷举所有解,可以画一颗解空间树

解空间树的基本结构:for循环横向遍历,递归纵向遍历,回溯不断调整结果集

回溯比暴力穷举高明的地方在于,回溯可以随时判断当前状态是否符合问题的条件,一旦不符合条件就退回到上一个状态,省去了继续往下探索的时间

状态的返回只有当目前结点不再满足问题的条件或者我们已经找到了问题的一个解时,才会返回,否则会以深度优先一直在解空间树内一直遍历下去

回溯算法经常拿来解决以下问题:

  1. 组合问题
  2. 排列问题
  3. 切割问题
  4. 子集问题
  5. 棋盘问题

对于一些问题如果其解空间过大,那么即使用回溯也有很高的时间复杂度,因为回溯会尝试戒空间树内所有的分支,所以对于这类问题,有一些优化方法比如剪枝策略或启发式搜索策略

剪枝策略:判断当前的分支树是否符合问题的条件,如果当前分支树不符合条件,那就不再遍历这个分支里所有的路径

启发式搜索策略:给回溯法搜索子节点的顺序设定一个优先级,按照此顺序去遍历更有可能找到问题的解

回溯函数的组成:回溯出口(停止条件),回溯body,状态返回(如果当前状态不满足条件,返回到前一个状态);iPad上有解回溯题的模版

一般回溯的问题有三种:

  1. Find a path to success 有没有解
  2. Find all paths to success 求所有解(求所有解的个数、求所有解的具体信息)
  3. Find the best path to success 求最优解

像括号问题或是那种成对匹配问题,可以转化成数学判定:在所有的可能的组合中,找出合法的组合,如何找出合法的组合其实就是通过设置的数学判定条件去筛选(#22)

你可能感兴趣的:(算法,leetcode)