问题引出:
leetcode NO.70
再做这类题的时候 如果无从下手:
可以考虑
1.暴力法能否解决 不行的话就考虑下面的方法
2.因为问题比较复杂 比较绕 那么就先只看基本情况 在考虑采用数学归纳法 归纳出一般性
3.找逻辑 找最近重复子问题,也就是要去找问题中重复的部分
——可以考虑采用递归 或动态规划
递归
1.最普通的傻递归 O(2^n) 而且可能引发StackOverflow
// 方法一:直接递归
// 但是这道题有点和斐波那契数列稍稍不符合的地方是 f0=0 而数列中f0=1;
class Solution {
public int climbStairs(int n) {
if(n == 0) { //不同处 f0=0,它有点点不符合斐波那契数列的思想 而数列中f0=1;
return 0;
}
if(n == 1) {
return 1;
}
if(n == 2) {
return 2;
}
return climbStairs(n-2) + climbStairs(n-1);
}
}
2.加数组缓存的递归 (前人栽树后人乘凉LOL)
//这个方法是可以的!! 但是只是适用于在输入44之前的数
// 方法二:记忆化递归 有缓存的思想 创一个数组 每次把递归的结果记录下来 方便下一次递归使用
//每当函数再次被递归调用时,我们就直接从 memo 数组返回结果。
// 但是这道题有点和斐波那契数列稍稍不符合的地方是 f0=0 而数列中f0=1;
class Solution {
public int climbStairs(int n) {
int memo[] = new int[n + 1];//防止递归时 溢出
if(n == 0) { //不同处 f0=0,它有点点不符合斐波那契数列的思想 而数列中f0=1;
memo[0] = 0;
return 0;
}
if(n == 1) {
memo[1] = 1;
return 1;
}
if(n == 2) {
memo[2] = 2;
return 2;
}
//接下来 和普通递归不同处是 每次先找缓存里面找我需要的递归式 是否已经在上一步算出来并且缓存了
if(memo[n] != 0) { //因为java数组默认全部填0 那么我这里如果判断n这个位置不是0 说明已经缓存过
return memo[n]; //那就返回即可
} else { //说明之前没缓存 需要缓存 给后面用 (前人栽树后人乘凉LOL)
int rst = climbStairs(n-2) + climbStairs(n-1);
memo[n] = rst;
return rst;
}
}
}
3.方法2plus 使用hashmap的缓存的递归
//这个方法也是可以的!! 但是只是适用于在输入44之前的数
// 方法三:记忆化递归plus 有缓存的思想 创一个数组 每次把递归的结果记录下来 方便下一次递归使用
//每当函数再次被递归调用时,和前一种方法的存储不同,前一种我们是从 数组memo[] 返回结果。 而现在可以使用hashmap(好处是使用它内部的查询优化)
// 但是这道题有点和斐波那契数列稍稍不符合的地方是 f0=0 而数列中f0=1;
class Solution {
public int climbStairs(int n) {
// int memo[] = new int[n + 1];//防止递归时 溢出 不用它了 用hashmap
Map<Integer, Integer> memo = new HashMap<>();
if(n == 0) { //不同处 f0=0,它有点点不符合斐波那契数列的思想 而数列中f0=1;
memo.put(0,0);
return 0;
}
if(n == 1) {
memo.put(1,1);
return 1;
}
if(n == 2) {
memo.put(2,2);
return 2;
}
//接下来 和普通递归不同处是 每次先找缓存里面找我需要的递归式 是否已经在上一步算出来并且缓存了
if(null != memo.get(n)) { //能取到值 说明已经缓存过
return memo.get(n); //那就返回即可
} else { //说明之前没缓存 需要缓存 给后面用 (前人栽树后人乘凉LOL)
int rst = climbStairs(n-2) + climbStairs(n-1);
memo.put(n,rst);
return rst;
}
}
}
4.方法四:动态规划(自底向上的动态规划) 还是 创一个数组 每次把规划的结果(子问题的解)记录下来 方便下一次父问题使用
实际上动态规划还有一种备忘录法(自顶向下)
class Solution {
public int climbStairs(int n) {
// int dp[] = new int[n+1];//当n取1 这就会报错 所以可以再开大点
int dp[] = new int[n+2];
if (n == 0) {
return 0;
}
// if (n == 1) {
// return 1;
// }
// if (n == 2) {
// return 2;
// } //实际可以用下一句代替 因为程序最好单一出口 或者少出口 但是写上的话 易读性更好
dp[1] = 1;
dp[2] = 2;
for(int i = 3; i <= n; i++) {
dp[i] = dp[i-2] + dp[i-1];
}
return dp[n];
}
}
// / 方法四:动态规划 还是 创一个数组 每次把规划的结果(子问题的解)记录下来 方便下一次父问题使用
// 但是这道题有点和斐波那契数列稍稍不符合的地方是 f0=0 而数列中f0=1;
5.动态规划plus (也称斐波那契数) 不需要数组 不需要每次把规划的结果(子问题的解)记录下来
只需要存最后的三个值即可 因为过程中每次子问题的解是动态变换的 也就是说作为解父问题的条件他是不断变换的 那么到了倒数的第二个第三个数 肯定是可以成为最后一个数的子问题的解
//复习前一天:
// 方法五: 动态规划 但不使用循环 无需记录过程中的数 但是只需要记住最后的三个数即可 因为过程中每次子问题的解是动态变换的 也就是说作为解父问题的条件他是不断变换的 那么到了倒数的第二个第三个数 肯定是可以成为最后一个属的子问题的解
class Solution {
public int climbStairs(int n) {
int rst = 0;
int f1 = 1;
int f2 = 2;
if(n == 0) { //不同处 f0=0,它有点点不符合斐波那契数列的思想 而数列中f0=1;
return 0;
}
if(n == 1) {
return 1;
}
if(n == 2) {
return 2;
}
for(int i = 3; i <= n; i++) {
rst = f2 + f1;
f1 = f2;
f2 = rst;
}
return rst;
}
}