1.递归要点
递归离不开树的遍历,一叉树,二叉树,三叉树,N叉树
有基础后只需记住一张图
这个图是单路径递归(最基础的递归),以下三个例题是每一步分两条路径递归(其实也是先递归一条,回溯后再递归另一条,但是可以看作两条路径同时进行)
2.递归、回溯法、DFS之间的联系
1. 递归是一种算法结构,回溯是一种算法思想,DFS和回溯法是用递归实现
- 回溯 = for循环里面有递归实现遍历的效果,来逐一试探
2. 多个连续并列的递归式可以达到搜索的目的。可以把回溯或者递归分为两类(对应有无return):
- 有返回值,递归一般是多个递归式的结果做某种运算后返回给上一级 ,即最后回溯完的结果为所求结果。
- 无返回值,在递归搜索过程中已经记录了搜索的路径,递归到底后输出搜索结果。
3. 回溯法回顾
3.递归和循环的关系
区别:
- 递归向下搜索完还有一个回溯的过程,使很多操作变得简单方便
联系:
- 对于不需要回溯的问题,如遍历输出完等。递归和循环(迭代)可以相互转化
- 递归和循环都可以完成遍历搜索,所以遍历分为递归遍历和迭代遍历
- 对于单个的递归和循环,递归的深度=循环的次数。
- 对于for循环里有递归(回溯法,DFS等)。递归的深度n=n个嵌套for循环。因此总循环次数=for循环次数的n次方
4.递归三大难点
1.递归方法一定要有结束条件(递归到底后的判断边界值,也是最小子问题的解)
2.递归方法的返回值(两类)
(1).有返回值用return(以下例题都是有返回值)
a. 如果递归方法返回值类型不是void,就需要return来返回数据,return返回数据是在递归结束后,回溯时一层一层返回的。上层需要用到下层返回的数据,一步步回溯合并成最终结果。
b. return的位置(1):方法中的边界条件处(2):方法最后(回溯法注意是for循环外)
(2).无返回值(void)
每一次递归到底,用一个全局变量记录,或者输出什么
//无返回值
void Backtrack(int t){//递归层次
if(t>n)
Output(x);
else{
for(int i=0;i<=1;i++){
x[t]=i;//记录痕迹
if(Constraint(t)&&Bound(t))
Backtrack(t+1);
}
}
}
3.递归方法的核心业务代码
紧挨着递归方法,前面是递归时执行的,后面是回溯时执行的
写业务代码时只考虑目前工作的执行顺序,当前需要做什么,不要乱了套
也就是我们解递归题的三部曲:
- 找整个递归的终止条件:递归应该在什么时候结束?
- 找返回值:应该给上一级返回什么信息?
- 本级递归应该做什么:在这一级递归中,应该完成什么任务?
5.递归解法
leetcode #120 三角形最先小路径和
递归,自顶向下 【超时】
int row;
public int minimumTotal(List<List<Integer>> triangle) {
row=triangle.size();
return helper(0,0, triangle);
}
private int helper(int level, int c, List<List<Integer>> triangle){
// System.out.println("helper: level="+ level+ " c=" + c);
if (level==row-1){
return triangle.get(level).get(c);
}
int left = helper(level+1, c, triangle);
int right = helper(level+1, c+1, triangle);
return Math.min(left, right) + triangle.get(level).get(c);
}
自顶向下, 记忆化搜索(备忘录)
int row;
Integer[][] memo;
public int minimumTotal(List<List<Integer>> triangle) {
row = triangle.size();
memo = new Integer[row][row];
return helper(0,0, triangle);
}
private int helper(int level, int c, List<List<Integer>> triangle){
// System.out.println("helper: level="+ level+ " c=" + c);
if (memo[level][c]!=null)
return memo[level][c];
if (level==row-1){
return memo[level][c] = triangle.get(level).get(c);
}
int left = helper(level+1, c, triangle);
int right = helper(level+1, c+1, triangle);
return memo[level][c] = Math.min(left, right) + triangle.get(level).get(c);
}
leetcode #10. 正则表达式匹配
/*
如果模式串中有星号,它会出现在第二个位置,即 \text{pattern[1]}pattern[1] 。这种情况下,我们可以直接忽略模式串中这一部分,或者删除匹配串的第一个字符,前提是它能够匹配模式串当前位置字符,即 \text{pattern[0]}pattern[0] 。如果两种操作中有任何一种使得剩下的字符串能匹配,那么初始时,匹配串和模式串就可以被匹配。
*/
public class Main {
public static void main(String[] args) {
String s = "abc";
String p = ".*";
System.out.println(isMatch(s, p));
}
public static boolean isMatch(String s, String p) {
if (p.isEmpty()) {
return s.isEmpty();
}
boolean firstisMatch = (!s.isEmpty()&& (s.charAt(0) == p.charAt(0) || p.charAt(0) == '.'));
if (p.length() >= 2 && p.charAt(1) == '*') {
return (isMatch(s, p.substring(2)) || firstisMatch && isMatch(s.substring(1), p));
} else {
return isMatch(s.substring(1), p.substring(1));
}
}
}
递归解法是为了进一步学习递归,深入理解递归的思想
递归过程
两个递归式相或
leetcode #70 爬楼梯
/*
方法一:暴力法
算法
在暴力法中,我们将会把所有可能爬的阶数进行组合,也就是 1 和 2 。而在每一步中我们都会继续调用 climbStairsclimbStairs 这个函数模拟爬 11 阶和 22 阶的情形,并返回两个函数的返回值之和。
climbStairs(i,n)=(i + 1, n) + climbStairs(i + 2, n)
climbStairs(i,n)=(i+1,n)+climbStairs(i+2,n)
其中 ii 定义了当前阶数,而 nn 定义了目标阶数。
*/
public class Solution {
public int climbStairs(int n) {
climb_Stairs(0, n);
}
public int climb_Stairs(int i, int n) {
if (i > n) {
return 0;
}
if (i == n) {
return 1;
}
return climb_Stairs(i + 1, n) + climb_Stairs(i + 2, n);
}
}
两个递归式相加
和回溯法的子集树很想,仔细想想不一样,注意区分。
多个相同的递归式只是参数不同就是DFS去搜索,有时候就可以改成成for+递归,提高代码优雅。
leetcode 21. 合并两个有序链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
/*
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 类似归并排序中的合并过程
ListNode dummyHead = new ListNode(0);
ListNode cur = dummyHead;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
cur.next = l1;
cur = cur.next;
l1 = l1.next;
} else {
cur.next = l2;
cur = cur.next;
l2 = l2.next;
}
}
// 任一为空,直接连接另一条链表
if (l1 == null) {
cur.next = l2;
} else {
cur.next = l1;
}
return dummyHead.next;
}
*/
public static ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1==null){
return l2;
}
if(l2==null){
return l1;
}
if(l1.val<l2.val){
l1.next=mergeTwoLists(l1.next, l2);
return l1;
}else {
l2.next=mergeTwoLists(l1,l2.next);
return l2;
}
}
}