问题分析(自顶向下)
- 首先看斐波那契数列,我们先自顶向下分析,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);
}
最后:
制作不易,转载请标明出处
点个赞是对我莫大的鼓舞~~