n步问题求解算法:斐波那契拓展|青蛙跳台阶拓展|三步问题拓展|多步问题拓展|高精度|大整数支持|自顶向下分析|自底向上求解

问题分析(自顶向下)

  • 首先看斐波那契数列,我们先自顶向下分析,f(n)其实是f(n-1)和f(n-2)的和,我们如果画成树,就会发现底层叶子节点都是 f(0)=0 和 f(1)=1。
  • 我们再看经典的青蛙问题,每次跳1~2步,跳一步的情况+跳两步的情况,f(n)=f(n-1)+f(n-2),其实本质也是斐波那契数列。(初始值及其含义不一样)
  • 最后看三步问题,每次跳1~3步,跳一步的情况+跳两步的情况+跳三步的情况,f(n)=f(n-1)+f(n-2)+f(n-3),很像斐波那契数列。(初始值及其含义不一样)

问题求解(自底向上)

我们其实很容易想到递归实现,但是这样很多叶子节点,例如 f(0) 和 f(1) 这些基值,都会被重复计算到。所以我们采用自底向上求解方法!

写在前面:(如果看不懂再回来看)

  • 为什么青蛙和三步问题的初值要这么设置?我们初始值都从f(0)开始设置,其实在青蛙中也可以设置f(-1)=0,f(0)=1,这样我们分析出的树就会多一层,但由于是自底向上求解,所以只多出了一步:f(1)=f(0)+f(-1)=1+0=1,这对我们是没什么影响的。
  • 初值的数量为什么青蛙是2个,三步问题是3个?因为对于青蛙问题,青蛙最多跳两步,在算法中f(1)会被直接返回一个1,所以不会计算到f(1)=f(0)+f(-1)上,而我们有了f(0)和f(1),这两种叶子,就可以从f(2)求到f(n)直到构造出整颗树。三步问题就有三种叶子来构造求解整棵树。
  • 首先看斐波那契数列,我们初始化f(0)=0,f(1)=1,然后求 f(2) = f(1)+f(0)=1,这样可以一直求到f(n)
  • 我们再看青蛙问题,但这次我们初始化 f(0)=1,f(1)=1,这表示如果剩下了0个或1个台阶,都只有一种跳法。例:f(2) = f(1)+f(0)=2,表示还有2个台阶时,跳一步之后剩1个,所以只有一种情况(f(2-1)=f(1)=1),跳两步之后剩0个,也只有一种情况(f(2-2)=f(0)=1)。用同样的思路,我们可以求解到f(n)
  • 最后看三步问题,参考青蛙问题,初始化的时候f(0)=1,f(1)=1,f(2)=2

代码

青蛙问题:https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/
斐波那契数列:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/
三步问题:https://leetcode-cn.com/problems/three-steps-problem-lcci/

拓展:多步问题

其实从上面的求解不难看出规律:一次可以跳多少个步(这里用maxStep表示)就初始化多少个值(叶子),其他都和青蛙和多步问题差不多。

于是我们有了求解思路:然后开辟maxStep大小的空间(这里用a[]数组存放),每一次迭代都将a数组中所有的值相加,然后循环移动位置(当然用一个指针标记更好)将最小(最早进来的)的去掉,相加得到的值放进来。这样迭代若干次之后就是最终结果。

细节:

  • 每一次可以跳的最大步数maxStep,如果超过了赛道的最大长度(这里用n表示),那么maxStep可以用n的值,效果一样。
public static int waysToStep(int maxStep, int n) {
	 // 暂时规定求解范围,因为结果较大int无法存储,double又精度不够
	 if (maxStep < 0 || maxStep > 7 || n > 100 || n < 0) {
         return 0;
     }
     // 最大步数大于n,效果和n一样
     if (maxStep > n) {
         maxStep = n;
     }
     // 初值(叶子)
     int[] a = new int[maxStep];
     a[0] = 1;
     for (int i = 1; i < maxStep; i++) {
         for (int j = 0; j < i; j++) {
             a[i] += a[j];
         }
     }
     // 开始循环求解
     int tmp = a[maxStep - 1];
     // 这里 n>2 是测试出来的结果,理解上难,但省去了for循环的i变量
     while (n > 2) {
         int i;
         for (i = 0; i < maxStep - 1; i++) {
             tmp += a[i];
             // 数组移位
             a[i] = a[i + 1];
         }
         a[i] = tmp;
         n--;
     }
     return tmp;
 }

多步问题优化(业务级别)

一般这种算法数据极大,又要求不能丢失精度,所以我们使用 java.math.BigDecimal 类:(这里直接按api来改就行了)

直接看代码+注释:

// 数字过大,返回String
public static String waysToStep(int maxStep, int n) throws Exception{
        // 测试后的比较快的求解范围
        // 后续可以用数学模型判断性能与maxStep、n的关系,以此做是否执行的判断条件
        if (maxStep < 0 || maxStep > 1000 || n > 10000 || n < 0) {
            throw new Exception("valid input : maxStep is in [0,1000],n is in [0,10000]");
        }
        if (maxStep > n) {
            maxStep = n;
        }
        BigDecimal[] a = new BigDecimal[maxStep];
        a[0] = BigDecimal.valueOf(1);
        for (int i = 1; i < maxStep; i++) {
            a[i] = new BigDecimal(0);
            for (int j = 0; j < i; j++) {
                a[i] = a[i].add(a[j]);
            }
        }
        BigDecimal tmp = new BigDecimal(String.valueOf(a[maxStep - 1]));
        while (n > 2) {
            int i;
            for (i = 0; i < maxStep - 1; i++) {
                tmp = tmp.add(a[i]);
                // 循环移位,但只改变 BigDecimal引用,能耗不大
                a[i] = a[i + 1];
            }
            a[i] = tmp;
            n--;
        }
        return String.valueOf(tmp);
    }

最后:
制作不易,转载请标明出处
点个赞是对我莫大的鼓舞~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值