如何优雅计算菲波那切数列,时间复杂度为O(log(n))

首先看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就会明白,如果有小伙伴有更好或者是更美的方式,跟我一起讨论哈。建议第三种方式,观其大略即可,真有可以应用的时候再去细想,掌握第二种动态规划即可。

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值