递归与回溯
一句话讲递归与回溯
递归:自己调用自己。本质就是找到前后的联系,找到递归的公式。
回溯:执行一次深度优先遍历(DFS),一条路走到底,走不通的时候,返回回来,继续执行,一直这样下去,直到回到起点。
递归
一般情况为:
if (终止条件) {
return;
}
recursion(参数1);
例子:阶乘
-
阶乘
public int recursion(int n) { //终止条件 if (n == 1) return 1; //调用自己 return n * recursion(n - 1); }
递归过程:
求f(5)的时候,只需要求出f(4)即可,如果求f(4)我们要求出f(3)……,一层一层的调用,当n=1的时候,我们直接返回1,然后再一层一层的返回,直到返回f(5)为止。
一些较实际的情况:
if (终止条件) {
return;
}
可能有一些逻辑运算
recursion(参数1);
可能有一些逻辑运算
recursion(参数2);
……
recursion(参数n);
可能有一些逻辑运算
例子:反转链表
-
反转链表
public static void reverseList(ListNode root) { //(终止条件) if (root == null) return ; //(递归调用)先打印下一个 reverseList(root.next); //(逻辑处理)把后面的都打印完了在打印当前节点 System.out.print(root.val + " "); }
结果如下:
回溯
回溯的本质,其实是在递归基础上进行了改进
一般情况为:
if(不满足继续递归查找的条件,通常为界限判断)
return;
if(满足查找条件)
return 这个值/节点;
递归左边
递归右边
递归结果判断-回溯
例子:矩阵中的路径(剑指Offer12)
判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。
示例:
输入:board = [
["a","b"],
["c","d"]
],
word = "abcd"
输出:false
- 思路:回溯算法(DFS+剪枝)。遍历矩阵中所以字符,先朝一个方向搜索到底,再回溯至上个节点,再沿另一方向搜索,以此类推。在搜索中,遇到匹配不成功(如索引越界、此元素已访问、此矩阵元素和目标字符不同)的情况就立即返回。
- 代码实现
class Solution {
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
for(int i = 0; i < board.length; i++) {
for(int j = 0; j < board[0].length; j++) {
if(dfs(board, words, i, j, 0)) return true;
}
}
return false;
}
//回溯
boolean dfs(char[][] board, char[] word, int i, int j, int k) {
//边界条件(越界、与目标元素不同)
if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;
//全部匹配完成
if(k == word.length - 1) return true;
char temp = board[i][j];
board[i][j] = '\0';//将当前元素标记为'\0',不可再被访问
//递归上下左右
boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) ||dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
board[i][j] = temp;//恢复其本身值
//递归结果判断-回溯
return res;//上面4个方向,只要有一个能查找到,就返回true
}
}
例子:二叉搜索树的最近公共祖先(剑指Offer68-Ⅱ)
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CUXcdhJx-1605416128009)(</images/OFfer68.PNG>)]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
-
思想:通过递归对二叉树进行后序遍历,当遇到节点 p 或 q 时返回。从底至顶回溯,当节点 p, q 在节点 root 的异侧时,节点root 即为最近公共祖先,则向上返回 root 。
p,q和root有三种情况:
1). p,q在root左右,返回root
2). p,q都在root左,返回lowestCommonAncestor( root.left, TreeNode p, TreeNode q)
3). p,q都在root右,返回lowestCommonAncestor( root.right, TreeNode p, TreeNode q)
-
递归终止条件
if(root == null) return null;//越过叶子节点,返回null
if(root == p) return p;//找到p,返回p
if(root == q) return q;//找到q,返回q -
递归体
递归左子节点,返回值为left
TreeNode left = lowestCommonAncestor( root.left, TreeNode p, TreeNode q);
递归右子节点,返回值为right
TreeNode right = lowestCommonAncestor( root.right, TreeNode p, TreeNode q);
-
递归结果
1).left == null && right == null
两边都没找到,返回null
2).left == null
右边找到,返回right
3). right == null
右边找到,返回left
4).left != null && right != null
说明p,q在root两侧,返回root
-
-
图文过程详解
-
代码实现
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//终止条件
if(root == null || root == p || root == q) return root;
//递归体
TreeNode left = lowestCommonAncestor( root.left, p, q);
TreeNode right = lowestCommonAncestor( root.right, p, q);
//递归结果判断-回溯
if(left == null && right == null) return null;
if(left == null) return right;
if(right == null) return left;
return root;
}
}
结束语
算法小白对递归和回溯的理解只到这里,希望随着刷的题越来越多,理解也越来越清晰透彻,到时再来分享!