1.1 递归介绍
递归是一种编程技巧,一种解决问题的思维方式;严格来说,递归并不是一种算法。简单地说,就是如果在函数中存在着调用函数本身的情况,这种现象就叫递归。
递归的思想就是,将大问题分解为小问题来求解,然后再讲小问题分解为更小的问题。这样一层一层的分解,直到问题规模被分解的足够小,不用继续分解,可以直接计算结果为止。
如果把这个一层一层分解过程画成图,他其实就是一棵树。我们给这棵树起一个名字,叫做递归树。
递归在”归”的过程中,符合后进先出的规则,所以需要用一个堆栈的数据结构。递归过程中函数调用会自动产生栈帧,当函数帧栈的深度越来越大的时候,栈也越来越大,如果递归没有终止条件,就会爆栈。所有基于递归思想实现的算法,第一步要思考的就是递归的终止条件。
分治算法和动态规划很大程度上是递归思想基础上的,解决更具体问题的两类算法思想。
进一步剖析「递归」,先有「递」再有「归」,「递」的意思是将问题拆解成子问题来解决, 子问题再拆解成子子问题,…,直到被拆解的子问题无需再拆分成更细的子问题(即可以求解),「归」是说最小的子问题解决了,那么它的上一层子问题也就解决了,上一层的子问题解决了,上上层子问题自然也就解决了。
递归的一般结构:
void func() {
if(符合边界条件) {
///
return;
}
//某种形式的调用
func();
}
以如下代码为例:
以阶乘函数为例,如下, 在 factorial 函数中存在着 factorial(n - 1) 的调用,所以此函数是递归函数:
public int factorial(int n) {
if (n < =1) {
return 1;
}
return n * factorial(n - 1)
}
以6的阶乘f(6) 为例来看下它的「递」和「归」:
求解问题 f(6), 由于 f(6) = n * f(5), 所以 f(6) 需要拆解成 f(5) 子问题进行求解,同理 f(5) = n * f(4) ,也需要进一步拆分,… ,直到 f(1), 这是「递」,f(1) 解决了,由于 f(2) = 2 f(1) = 2 也解决了,… f(n)到最后也解决了,这是「归」,所以递归的本质是能把问题拆分成具有相同解决思路的子问题,。。。直到最后被拆解的子问题再也不能拆分,解决了最小粒度可求解的子问题后,在「归」的过程中自然顺其自然地解决了最开始的问题。
参考:https://www.jianshu.com/p/b2d2edb4ba5b
1.2 基本步骤
(1) 定义一个函数,明确函数功能
(2) 寻找问题与子问题之间的关系(递推公式)
(3) 将递推公式在定义的函数中实现
(4) 推导时间复杂度,判定是否可以接受,无法接受更换算法
1.3 代表题目
1.3.1 爬楼梯(#70)
class Solution {
public int climbStairs(int n) {
int[] stairs = new int[n + 1];
return climb(n, stairs);
}
private int climb(int n, int[] stairs) {
if (n == 1) return 1;
if (n == 2) return 2;
if (stairs[n] > 0) {
return stairs[n];
}
stairs[n] = climb(n - 1, stairs) + climb(n - 2, stairs);
return stairs[n];
}
}
1.3.2 青蛙跳台阶(#面试题10-II)
1.3.3 斐波那契数列(#509)
class Solution {
public int fib(int N) {
int[] fibs = new int[N + 1];
return process(N, fibs);
}
private int process(int n, int[] fibs) {
if (n == 0) return 0;
if (n == 1) return 1;
if (fibs[n] > 0) {
return fibs[n];
}
fibs[n] = process(n-1, fibs) + process(n-2, fibs);
return fibs[n];
}
}
1.4 触类旁通
1.4.1 反转二叉树(#226)
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
root.left = right;
root.right = left;
return root;
}
}
1.4.2 路径总和(#112)
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null) {
return false;
}
if (root.left == null && root.right == null) {
return root.val == sum;
}
int remain = sum - root.val;
return hasPathSum(root.left, remain) || hasPathSum(root.right, remain);
}
}