题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
初始代码
class Solution { public: int jumpFloorII(int number) { } };
解法1
本题牛客网给了一个“贪心”的标签, 在在做这道题之前, 首先就被它的阵势给吓住了。。。什么“变态”啦, 什么“贪心”算法啦。脑子里一直在想贪心是个什么样的定义,最优子结构?还有什么?本题的最优子结构又是体现在哪里?
题目还未开始动,自乱阵脚是一件比较可怕的事。
最好的解决办法还是先动手动脑,看看能有什么样的解题思路。不要被这个纸老虎给挡住去路啦。
青蛙一次可以跳1~n级,在题目中我们最终需要跳n级,那么还是顺着[JZ8]跳台阶一题的思路,我们首先关注最后一跳。
现在青蛙有了多种选择,最后一跳可以只跳了1级,也可以跳了2到n中的任意一个数值的级数。
使用表达式抽象出来就是:f(n) = f(n-1) + f(n-2) + ... + f(1) + 1
。
这个表达式很有特殊性。
它也作为一个递归式,其递归出口是f(1) = 1
。
因此我们可以轻易地得到:
f(2) = f(1) + 1 = 2 f(3) = f(2) + f(1) + 1 = 4 ...
在代码实现中,就遵照这个思路给出答案。由于如果从底向上计算,那么计算f(k)
的时候,f(k-1)
到f(1)
之间的值其实都是计算过的。可以考虑使用一个数组来保存之前计算过的值,从而避免计算的重复,提高效率。
例如,使用一个长度为number-1
的整型数组nums
,最开始我们只知道nums[1] = 1
,然后我们从i=2
开始一直到i=number
,每计算一次就把结果保存在nums[i]
中,从而避免了了在计算下一个数的时候还重复计算前面的。
但其实在此基础上还有个明显的优化点。那便是我们在递归式中其实并不关注每个前面的值具体是多少,例如当计算f(i)
时,我们真正关注的是f(1)+f(2)+...+f(i-1)
这个和式的值是多少,至于和式中每个分量的值是多少并不需要知道。
因此,我们对上面的思路进一步优化,不再开辟一个数组来保存每一个f(i),i=1,2,...,n
。只需一个变量来保存迄今为止f(1)+f(2)+...+f(i-1)
这个和式的值sum
,另外一个变量就基于sum
的值来计算得到f(i)
,即f(i)=1+sum
。
下面是代码:
class Solution { public: int jumpFloorII(int number) { if(number == 1) return number; int sum = 0, res = 1; for(int i=2; i <= number; i++) { sum = 1 + res; res += sum; } return sum; } };
解法2
解法1整个思路下来并没有多费脑筋,而最开始萦绕在脑海中的“贪心”标签,也没有很体会到贪心在哪,或许我这个解法不是贪心?
查找已有的题解,看是否有“贪心”策略,但发现置顶的官方题解中都没有特别提到“贪心”的使用。那就不纠结了吧,日后遇到典型的通过贪心来解决的题目的时候再来回顾本题,看是否有新的理解。
官方题解中透露了一个在解法1上的进一步优化策略。上面已经得到:
f(n) = f(n-1) + f(n-2) + ... + f(1) + 1;(1)
那么也同时有:
f(n-1) = f(n-2) + f(n-3) + ... + f(1) + 1; (2)
(1)式减去(2)式再移项,得到:
f(n) = 2 * f(n-1); (3)
!!!
所以说,本题归结到底就是一个二乘递归式咯?
接着就可以根据式(3)建立递推来得到一个新的解法;
同时,也可以更进一步地,推导出:f(n) = 2^(n-1)
两种思路的代码实现如下:
class Solution { public: int jumpFloorII(int number) { if(number == 1) return number; return jumpFloorII(number - 1) * 2; } };
class Solution { public: int jumpFloorII(int number) { return 1 << (number - 1); } };