f(递归法){
return f(递归法-1) +f(递归法-2);
}
一、二叉树的最大深度
先要说一点:所有二叉树的套路都是一样的
void traverse(TreeNode root) {
// 前序遍历
traverse(root.left)
// 中序遍历
traverse(root.right)
// 后序遍历
}
二叉树的算法思想的运用广泛,甚至可以说,只要涉及递归,都可以抽象成二叉树的问题。
在这道题中,traverse(TreeNode root)
就是maxDepth(TreeNode root)
而递归需要找到边界条件:当root为空,也即是到达叶子节点的时候结束,对应代码if(root == null) return 0;
如果root不为空,那么就应该遍历他的左右子节点的深度,选择返回值最大的,然后再加上自身,对应代码时Math.max(left, right) + 1
知道了这两个条件代码就可以写出来啦
class Solution {
public int maxDepth(TreeNode root) {
//递归终止条件
if(root == null) {
return 0;
} else {
//左子树的最大深度
int left = maxDepth(root.left);
//右子树的最大深度
int right = maxDepth(root.right);
//选择左右子树最大的深度再加一
return Math.max(left, right) + 1;
}
}
}
写完代码,再来想一想时间复杂度和空间复杂度都是多少把
因为是左右子树都遍历了,每个节点都遍历一遍了,所以时间复杂度是O(n)
递归函数需要栈空间,栈空间取决于递归的深度,所以空间复杂度等于二叉树的最大高度,O(height)
二、不同路径
递归第一步就是先找到边界条件,也就是什么时候结束递归:当机器人走到右下角的时候,递归结束。
为了更形象一些,添加了两个参数i和j,代表机器人所在的位置,m和n就是网格右下角,也就是目标位置。
所以现在递归的边界条件很清楚,就是当i=m && j==n
的时候,机器人到达右下角的时候,递归结束 ,对应代码是:if (i == m && j == n) return 1;
,机器人不移动也算一条路径所以要返回1。
如果机器人越界了,则返回0,对应代码:if (i > m || j > n) return 0;
递归嘛,写完边界条件,就要继续加递归函数了,因为机器人从左上角往右下角走,所以他只有两种移动方向,向下走和向右走。
函数中的map,可以先忽略不看,不影响
机器人向下走有几条路径:int down = path(i + 1, j, m, n, map);
机器人向右走有几条路径:int right = path(i, j + 1, m, n, map);
总的路径就是向下和向右加起来,对应代码int sum = down + right;
hxdm,看到在这里是不是觉得就完事了,写代码的手已经跃跃欲试了,都在喊我可以我可以,结果一些代码就扑街了……😂
这里的超出时间限制,因为有大量的重复计算,直接上图
如果我们要计算f(6),其实只要计算一次 f(5) f(4) f(3) f(2) f(1) ,但是在图中可以看到我们有好多重复的计算。
为了解决重复计算,就用Map把计算好的存起来,如果用过就直接从Map里拿,如果Map里没有,再计算然后放到Map里面,(此处@MySQL和Redis两位好兄弟)
流程图来梳理一遍逻辑:
代码:
class Solution {
public int uniquePaths(int m, int n) {
//表示从坐标(1,1)到(m,n)有几条路径
return path(1, 1, m, n,new HashMap<>());
}
public int path(int i, int j, int m, int n, Map<String, Integer> map) {
if (i > m || j > n) {
return 0;
}
if (i == m && j == n) {
return 1;
}
String key = i + "-" + j;
//如果已经遍历过,就直接返回,不必继续计算了
if (map.containsKey(key)) {
return map.get(key);
}
//向下走有几条路
int down = path(i + 1, j, m, n, map);
//向右走有几条路
int right = path(i, j + 1, m, n, map);
//向下的和向右的加起来 就是总路数
int sum = down + right;
//放入map中,避免重复计算
map.put(key, sum);
return sum;
}
}
最后虽然通过了,但是效率很低……
关键时刻还得看动态规划,这次递归法是小弟,动态规划是大哥😁
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
//第一列都是1
for (int i = 0; i < m; i++) {
dp[i][0] = 1;
}
//第一行都是1
for (int i = 0; i < n; i++) {
dp[0][i] = 1;
}
//递推公式
for (int i = 1; i < m; i++)
for (int j = 1; j < n; j++)
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
return dp[m - 1][n - 1];
}
}
三、数值的整数次方
这道题是要把幂次n分类讨论了:
- 如果n==0,返回1 x0 =1
- 如果n小于0,结果是1/(x-n) 比如x-2,就看做1/x2
- 如果n为奇数,结果是x*(xn-1) 比如x5 看做x * x4
- 如果n为偶数,结果是(x*x)n/2 比如x4 看做 x2 * x2
分类讨论,然后递归即可
class Solution {
public double myPow(double x, int n) {
//n为0
if (n == 0) {
return 1;
//n小于0
} else if (n < 0) {
return 1 / (x * myPow(x, -n - 1));
//n为奇数
} else if (n % 2 == 1) {
return x * myPow(x, n - 1);
//n为偶数
} else {
return myPow(x * x, n / 2);
}
}
}
递归几次复杂度就是多少
n为偶数,递归logn(2为底数)次
n为奇数,需要递归,让n减一次变成偶数
所以,时间复杂度和空间复杂度都是O(logn)