递归回溯问题及DFS/BFS相关总结
一、递归
1. 何为递归?
递归,即函数(方法)自己调用自己,亦或者称为套娃。
2. 递归的简单模板
递归不能无限的进行,因为递归的原理跟栈有关,如果无限的调用递归就会栈溢出,所以必须在一开始就设置递归的终止条件。如下面这样
public void recur(参数 0 ){
if 终止条件
return;
recur(参数 1);
}
但是不能写成下面这样
public void recur(参数 0 ){
recur(参数 1);
if 终止条件
return;
}
如果是这样写就会无限的递归下去,最后StackOverflow
3. 递归的简单例子
3.1 阶乘
public int factorial(int n){ if(n <= 1) return 1; else return n * factorial (n - 1); }
3.2 斐波那契数列
public int Fibonacci(int n){ if(n == 0) return 0; if(n == 1) return 1; else return Fibonacci(n - 1) + Fibonacci(n - 2); }
3.3 汉诺塔
public void hanoi(int n, char x, char y, char z){ if(n == 1){ System.out.printf("%c -> %c\n",x, z); } else { hanoi(n - 1,x, z, y); System.out.printf("%c -> %c\n",x, z); hanoi(n - 1,y, x, z); } }
4. 适合用递归的场合
如果一个问题可以拆分成更小的子问题,且子问题和原问题有相同的结构,就可以尝试用递归去写。但是要考虑空间的限制
5. 如何更好的理解递归
一开始接触递归总归会有点懵,因为原来写的代码都是显式的,你可以跟着代码理顺下来,或者一步步debug,但是递归是隐式的(借助栈)。
以阶乘为例来辅助理解,加入要求4的阶乘,用上面的程序会是怎样的流程呢?
f(4)
->4 * f(3)
->4 * (3 * f(2))
->4 * (3 * (2 * f(1)))
->4 * (3 * (2 * 1))
->4 * (3 * 2)
->4 * 6
->24
前面4行表示的是因为没有触发终止条件一直向下拆分成更小的子问题的过程,后面的表示触发终止条件后向上回弹代入的过程。递归就是一个先递进再回归的过程。如果想更好的理解递归,我的办法是从递归的终止条件向上一级一级的推导,可能会好理解一点。
二、回溯
1. 什么叫回溯算法
简单点来说回溯算法是一种解决问题的思想,它通常要通过递归来实现,所以放在一起复习,而且确实一开始很不好理解,需要多琢磨。
回溯算法的思路就是暴力求解,不停探索的解决问题思路。从起点出发,先朝着一个方向走,直到走不通的时候再回退一步重新做选择。这种走不通就退回再走的思路成为回溯法。结合生活的一个例子来说,猜一个一位数的密码,第一位先猜0,不对就再猜别的1-9,知道猜对。这种不停尝试的枚举法就是回溯。
2. 回溯算法实例
最近做题经常碰到回溯相关的题目,后面的DFS也跟回溯联系紧密,就刚好整理复习一下。
力扣78.子集
问题描述:给定一组不含重复元素
的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[1],
[1,2],
[1,2,3],
[1,3],
[2],
[2,3],
[3],
[]
]class solution{ //定义全局变量的话就不用了给函数传参了 //视个人习惯而定 List<List<Integer>> res = new ArrayList<>(); List<Integer> list = new ArrayList<>(); public List<List<Integer>> subsets(int[] nums){ dfs(nums, 0); return res; } /** *param nums:传入的数组 *param index:可以理解为层数 */ public void dfs(int[] nums, int index){ res.add(new ArrayList<>(list)); for(int i = index; i < nums.length; i++){ list.add(nums[i]); dfs(nums, i + 1); //避免分支污染 list.remove(list.size() - 1); } } }
力扣46.全排列
问题描述:给定一个没有重复
数字的序列,返回其所有可能的全排列。
示例:
输入: nums = [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1],
]class solution{ List<List<Integer>> res = new ArrayList<>(); public List<List<Integer>> permute(int[] nums){ dfs(nums,new ArrayList<>()); return res; } public void dfs(int[] nums, List<Integer> list){ if(list.size() == nums.length) res.add(new ArrayList<>(list)); for(int i = 0; i < nums.length; i++){ if(!list.contains(nums[i])){ list.add(nums[i]); dfs(nums, list); list.remove(list.size() - 1); } } } }
力扣39.组合总数
问题描述:给定一个无重复元素
的数组candidates
和一个目标数target
,找出candidates
中所有可以使数字和为target
的组合。
candidates
中的数字可以无限制重复被选取。
示例:
输入: candidates = [2,3,6,7], target = 7,
所求解集:
[
[7],
[2,2,3],
]class solution{ List<List<Integer>> res = new ArrayList<>(); public List<List<Integer>> combinationSum(int[] candidates, int target){ dfs(candidates, target, new ArrayList<>()); return res; } public void dfs(int[] nums, int target, List<Integer> list, int index){ if(target == 0) res.add(new ArrayList<>(list)); for(int i = index; i < nums.length; i++){ if(nums[i] > target) continue; list.add(nums[i]); dfs(nums,target - nums[i],list,i); list.remove(list.size() - 1); } } }