首先看leetcode上一道简单的题目:
leetcode上的爬楼梯的问题
题目大致描述:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
f(n) = f(n-1)+f(n-2); // f(n)为n节台阶的走法。n节台阶的走法,为n-1和n-2台阶走法之和。
这道题解法很多,我这里主要分分享三种,其实第三种,我也是查一些资料才知道能这么解答,今天写这个主要是整理自己的思路,方便以后复习,温故而知新,不多bb。
解法1.递归
public int climbStairs(int n) {
//递归的方法,是逆向推到,时间复杂度为O(2^n)
//1.递归的终止条件
int result = 0;
if(n == 1)return 1;
if(n == 2)return 2;
//2.当前层的操作
result=climbStairs(n-1)+climbStairs(n-2);
//3.调用递归方法
//4.清除成员变量(这里不需要)
return result;
}
递归解法的时间复杂度为O(2^n),小伙伴们可以画出递归状态树即可得到时间复杂度,如果对递归写法不熟练的小伙伴,可以看一下我上诉代码注释的部分,做一道递归题的思路,大致脱离不开这四个步骤,剩下多练练就可以。
上诉递归的方法,存在大量的重复计算,所以时间复杂度很高,例如对于f(n-2),f(n)需要计算一遍,f(n-1)又需要计算一遍。太费劲了。
进一步优化解法2。
解法2:动态规划
动态规划三步走:
1.寻找最优子结构问题
2.定义状态数组(数组维数,如果能定义好状态数组就可以很方便写出状态方程)
3.写出状态方程
public int climbStairs_dp(int n) {
//采用动态规划时间复杂度为O(n),额外的空间复杂度为O(n)
if(n==1)return 1;
// 状态数组(采用数组,可以记录前两项的走法,不用再重复计算)
int[] dp = new int[n];
dp[0]=1;
dp[1]=2;
for(int i=2;i<dp.length;i++){
// 状态方程
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n-1];
}
采用状态数组记录计算过的值,需要的时候直接取即可,时间复杂度降为O(n),但是额外的空间复杂度需要O(n),我们先不管时间复杂度,如果让你优化的话,你还能继续优化么?
状态数组可以进一步优化,因为每一次我们只是需要前两项的数字,可以不使用数组的形式。
public int climbStairs_dp_ii(int n) {
int dp_before_2=1;//前两个台阶的走法
int dp_before_1=2;//前一个台阶的走法
if(n == 1)return dp_before_2;
if(n == 2)return dp_before_1;
int dp = 0;
for(int i=2;i<n;i++){
// 状态方程
dp = dp_before_2 + dp_before_1;
dp_before_2 = dp_before_1;
dp_before_1 = dp;
}
return dp;
}
如果想继续降低时间复杂度,请看解法3;
解法3:采用幂函数的求解达到时间复杂度为O(log(n))
这种方法个人感觉很难想到,也不知道哪个牛人想出这种解法的
主要是两方面:1.幂函数的求解。
2.dp方程的进一步演变。
第一步:dp方程的演变:(我不太会使用编辑器提供的数学公式,在本上大致写了一下,演变方式,采用二维数组的方式,我看过其他人的文章,可以进一步去演变到n次方,但是个人感觉不好理解,演变到我这种程度这道题就可以解出来了)。
字迹太丑,大哥大姐凑活看。。。。
第二步:幂函数
求幂函数的方法很多,不一定按照我的方法,可以自己写一个,这样影响深刻。这里我就不单独写出来
最后求解方法:
public int climbStairs_pow(int n) {
if(n == 1)return 1;
if(n==2)return 2;
int[][] x={{1,1},{1,0}};//演变出的矩阵常量
int[][] mi = pow(x, n - 2);
int result = 2*mi[0][0]+mi[0][1];//f(2)为2,f(1)为1
return result;
}
// 计算x的n次幂
public int[][] pow(int[][] x,int n){
int[][] i={{1,0},{0,1}};//矩阵常量,相当于1
int[][] v=x;
if(n==1){
return x;
}
while(n != 0){
if(n%2 ==1){
i = multi(i,v);
n-=1;
}
v= multi(v,v);
n = n/2;
}
return i;
}
// 二维矩阵相乘。
private int[][] multi(int[][] v1, int[][] v2) {
int[] num1 = {v1[0][0]*v2[0][0]+v1[0][1]*v2[1][0],v1[0][0]*v2[0][1]+v1[0][1]*v2[1][1]};
int[] num2 = {v1[1][0]*v2[0][0]+v1[1][1]*v2[1][0],v1[1][0]*v2[0][1]+v1[1][1]*v2[1][1]};
return new int[][]{num1,num2};
}
第三种方法时间复杂度为O(log(n)),时间复杂度在幂函数的运算上。
上诉三种方法,是我在做斐波那契数列的整理,并且都在leetcode上测试过,不懂的小伙伴可以直接debug就会明白,如果有小伙伴有更好或者是更美的方式,跟我一起讨论哈。建议第三种方式,观其大略即可,真有可以应用的时候再去细想,掌握第二种动态规划即可。