递归学习和总结

1 引言

  所谓递归,就是在运行过程中调用自己,通常是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,通过采用递归策略可以大大地减少程序的代码量。递归的这种策略往往会应用于回溯和分治算法,在回溯算法中,往往采用递归而不用循环,这主要是因为递归本身就是一个回溯的过程(入栈出栈)。

  下面将通过回溯和分治来进一步学习递归算法。

2 预备知识

2.1 回溯算法

  回溯法又称为试探法,但当探索到某一步时,发现原先选择达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法。

2.2 分治算法

  分治法的核心在于分-治-合,分是指将问题分解为规模更小的子问题,治是指将这些规模更小的子问题逐个击破,合是指将已解决的子问题合并,最终得出‘母’问题的解;分治法可以用循环和递归进行解决;

3 回溯

3.1 求子集

  已知一组数(无重复元素),求这组数可以组成的所有子集,结果中不可有重复的子集;

  思路:

  • 对于每个元素。都有试探放入和不放入两种选择
  • 假设数组[1,2,3,4,5],对于元素1,有放入不和不放入两种选择,放入时item=[1],然后递归对于剩余元素进行处理,不放入时item=[2],同样递归对于剩余元素进行处理;这里就体现了递归(子问题)和回溯的思想;
  • 从逻辑上来说,包括以下部分:递归出口,放结果,第一次递归(放元素的递归),回溯操作(退出元素),第二次递归(不放元素的递归)

  已知一组数(有重复元素),求这组数可以组成的所有子集,结果中不可有重复的子集;

  思路:

  • 当有重复元素时,如果按照上题的做法,就一定会出现重复的子集;重复可能是同一子集,顺序相同和不同;
  • 可以基于上一题的做法,先排序后用set去重,切记要先排序,这样就杜绝出现[1,2,2]和[2,1,2]无法去重的情况;(当set里面的元素是数组的时候,是否能够去重?)

3.2 组合数之和

  已知一组数(包括重复元素),求这组数可以组成的所有子集中,子集中的各个元素和为整数target的子集,结果中无重复的子集

  • 如果基于求子集,先求出所有子集,会出现超时的情况;假设第一个元素大于target,那么后续的递归都没有价值;
  • 如果基于搜索回溯,再进行剪枝操作,算法的性能会得到提升
  • 算法的参数会增加一个sum参数,用于记录目前结果的sum,方便与target比较;
  • 算法的逻辑:递归出口(包括边界条件和剪枝条件),放结果,第一次递归(放元素的递归),回溯操作(退元素,减sum),第二次递归(不放元素的递归)

3.3 生成括号

  已知n组括号,开发一个程序,生成这n组括号所有合法的组合可能。

  • n组括号有多少种组合的可能?在不考虑合法的情况,字符串中的每个字符有两种选择的可能,那么就有2^2n种可能;
  • 与求子集的题目相比,该题的两种选择是选左括号还是右括号,而求子集是选择或者不选择;
  • 不考虑合法的情况,程序的逻辑:递归出口,第一次递归(放左括号的递归),第二次递归(放右括号的递归);由于是选择左括号还是右括号,那么我们可以在参数中就可以体现出来;
  • 考虑合法的情况,有以下限制:左右括号都不能超过n,另外右括号不能多于左括号;因此程序可以设置两参数限制左右括号的个数,程序逻辑如下:递归出口,递归左括号(限制条件),递归右括号(限制条件)

3.4 N皇后

  将N个皇后摆放在N*N的棋盘,互相不可攻击,有多少种摆放方式,每种摆放方式具体是怎样的?所谓不可攻击,是指每个皇后的所在位置的横竖斜都不能再有皇后。

  • 如何设计算法和数据存储,体现放置皇后和更新棋盘的作用;二维数组代表棋盘;设置方向数组;
  • 模块1:放置皇后更新棋盘的操作;程序逻辑:在方向数组的基础上,循环操作;
  • 利用递归对棋盘的每一行放置皇后,放置时,按列顺序寻找可以放置皇后的列,若可以放置皇后,将皇后放置该位置,并更新mark标记数组,递归进行下一行的皇后放置;当该次递归结束后,恢复mark数组,并尝试下一个可能放皇后的列,恢复操作只需要递归之前保存mark数组即可
  • 设置皇后位置二维数组和皇后攻击范围数组;
  • 模块2:回溯算法;程序逻辑:递归出口,按顺序尝试第0列至第n-1列,首先判断是否可以放置皇后,然后存储放置之前的皇后攻击范围和皇后放置位置,接着放置皇后操作,然后进行递归下一行的皇后放置,进行回溯操作(恢复皇后攻击范围并恢复皇后放置位置);

3.5 回溯总结

  • 求子集问题和组合数问题考虑的是数组元素选择与不选择问题,那么使用两次递归,一次是选择该元素的递归,另一个是不选择该元素的递归;
  • 生成括号考虑的是选择左括号还是右括号的问题,那么使用两次递归,一次选择左括号的递归,一次是选择右括号的递归即可,需要注意的是递归有相应的限制条件,另外还有就是递归出口;
  • 上述三种类型的题目,都是是否选择的问题,这种问题会涉及两个递归函数,并且要注意递归的限制条件,以及两次递归之间可能涉及的回溯操作;
  • N皇后问题考虑的是判断是否可以放置皇后,如果不能放置,则回溯;需要注意的是,需要依次从每行的1-n列判断可能的放置皇后的位置,然后进行更新攻击范围的数组,如果需要回溯的时候,要注意恢复攻击范围和皇后上一状态位置;

4 分治

4.1 归并排序

  给定一个数组,对该数组进行排序(采用归并排序的算法)

  • 程序逻辑:递归出口(子问题足够小,直接求解),分解子问题,递归对子问题进行求解,合并子问题;
  • 程序的难点在于参数的理解,分解的时候需要申请数组空间,保存两个子问题的数组,然后递归求解,合并子问题的时候,需要将两个子数组合并到大的数组中;

4.2 逆序数

  已知数组nums,求新数组count,count[i]代表了在nums[i]右侧且比nums[i]小的元素的个数,例如nums=[5,2,6,1],count=[2,1,1,0];

  • 暴力破解:遍历每个nums[i],然后扫描右侧比它小的数,并记录;暴力破解时间复杂度为O(n)
  • 组合nums[i]和i组合为一个pair对,即pair[i]=<nums[i],i>,对pair对进行归并排序,在两子问题进行合并的过程中,需要对count数组进行加减操作;
  • 之所以采用归并排序的分治算法解决,是因为在已经归并过程中之后,就可以对count进行更新,需要注意的是第二个数组始终是在第一个数组的后面,因此只用更新第一个数组的count就可以;

4.3 分治总结

  • 所谓分治就是先分后治再合,上述中的归并排序,就是先分成子问题,然后递归求解(治),最后再合;
  • 对于求逆序数而言,之所以想到归并排序的方法,实际上是因为归并排序的特性,就是在已经排序好的两个子问题上,我们可以在归并中求得逆序数(合的那一个步骤);而且算法的复杂度降到了O(nlogn);

5 参考资料

  面试算法leetcode刷题班

转载于:https://www.cnblogs.com/dailinfly/p/9140615.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值