题目:
You are climbing a stair case. It takes n steps to reach to the top.
Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?
Note: Given n will be a positive integer.
Example 1:
Input: 2
Output: 2
Explanation: There are two ways to climb to the top.
1. 1 step + 1 step
2. 2 steps
Example 2:
Input: 3
Output: 3
Explanation: There are three ways to climb to the top.
1. 1 step + 1 step + 1 step
2. 1 step + 2 steps
3. 2 steps + 1 step
这道题又是以前做过的,典型的动态规划,而且上周刚在知乎上看到一篇介绍动态规划讲的很好的文章就是拿爬楼梯这个举例的,因此印象深刻,对于子问题的划分记的非常牢固了。由于每次只能上一个台阶或者两个台阶,因此n个台阶的走法数就是(n - 1)个台阶和(n - 2)个台阶的走法之和。
于是快乐地写出如下代码,时间和空间复杂度都是O(n),运行时间0ms:
class Solution {
public:
int climbStairs(int n) {
vector<int> steps;
steps.push_back(1);
steps.push_back(2);
for (int i = 2; i < n; i++) {
steps.push_back(steps[i - 1] + steps[i - 2]);
}
return steps[n - 1];
}
};
博客写着写着发现自己刚开始提交的代码实在是太不优雅了而且当时居然脑子短路没想到优雅的写法,终于把它改优雅了一些,之前一些小细节的问题就不复存在了,就不赘述了。翻了一下之前水算法作业的博客,目测当时又不是纯靠自己写出来的orz 这么说来还是有点小进步的?(虽然现在也很菜但是以前更菜啊哈哈哈)以为这道题就到此结束了,没想到当我点开solutions的那一刻,我才发现战斗才刚刚开始……
以下是3.24刷剑指时的分割线。
之前居然忘了说了这个的本质就是斐波那契数列,这次刚开始就直接写出了上次的解法,然后回顾了一下半年前面头条的时候我居然特么连斐波那契都差点没写出来orz 真是太丢人了吧!看了一下之前用的是constant space的解法,没有用到vector,当时如果脑子清醒用vector应该也不会在交换数字的时候又犯蠢了QAQ 现在想想真的是没脸见人了orz 下面来补一下斐波那契的解法吧:
1. 暴力递归,时间O(2^n),空间O(2^n):
class Solution {
public:
int Fibonacci(int n) {
if (n == 0) {
return 0;
}
else if (n == 1) {
return 1;
}
else {
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
}
};
2. 数组+循环,时间O(n),空间O(n):
class Solution {
public:
int Fibonacci(int n) {
vector<int> fib;
fib.push_back(0);
fib.push_back(1);
for (int i = 2; i <= n; i++) {
fib.push_back(fib[i - 2] + fib[i - 1]);
}
return fib[n];
}
};
3. 循环,时间O(n),空间O(1):
class Solution {
public:
int Fibonacci(int n) {
if (n == 0) {
return 0;
}
else if (n == 1) {
return 1;
}
int fib1 = 0;
int fib2 = 1;
for (int i = 2; i <= n; i++) {
int fib = fib1 + fib2;
fib1 = fib2;
fib2 = fib;
}
return fib2;
}
};
4. 矩阵乘方
据说存在这样一个数学公式:
把后面这个矩阵成为Q,那么只需要求出Q^(n-1)即可得到斐波那契数列的fib(n)。由于矩阵的乘方具有如下性质:
于是,采用递归的方法就可以将时间复杂度缩小到O(log n)。
由于不想自己写矩阵乘法,所以只好copy了leetcode上的java解法过来。代码时间复杂度:O(log n),空间复杂度O(1):
public class Solution {
public int climbStairs(int n) {
int[][] q = {{1, 1}, {1, 0}};
int[][] res = pow(q, n);
return res[0][0];
}
public int[][] pow(int[][] a, int n) {
int[][] ret = {{1, 0}, {0, 1}};
while (n > 0) {
if ((n & 1) == 1) {
ret = multiply(ret, a);
}
n >>= 1;
a = multiply(a, a);
}
return ret;
}
public int[][] multiply(int[][] a, int[][] b) {
int[][] c = new int[2][2];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j];
}
}
return c;
}
}
5. 通项公式
实际上,斐波那契数列已经有通项公式了:
所以就可以直接简单粗暴地套公式:
class Solution {
public:
int Fibonacci(int n) {
double sqrt5 = sqrt(5);
return (pow((1 + sqrt5) / 2, n) - pow((1 - sqrt5) / 2, n)) / sqrt5;
}
};
时间复杂度:O(log n),空间复杂度O(1)
4.6再来补充一下变态跳台阶,剑指上面没有,但是牛客网的剑指在线编程里面有。
题目:
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
这道题乍一看可吓人了,内心想着这可咋算啊,看了答案以后才发现原来这么简单……这道题一共有两种思路:
1. 动态规划思想
假如只有一级台阶,那么f(1) = 1
假如有两级台阶,那么可以先跳一级,剩下的跳法就是f(2-1) = f(1),也可以直接跳两级,剩下的跳法就是f(0) = 1(为什么设置为f(0) = 1呢,因为这也算一种跳法,如果设f(0) = 0的话那直接蹦到顶就不算一种方法了)
假如有n级台阶,那么可以先跳一级,剩下f(n-1),或者先跳两级,剩下f(n-2),直到先跳n-1级,剩下f(1),和直接跳上n级,剩下f(0),即,f(n) = f(n-1) + f(n-2) + ... + f(1) + f(0)
而f(n-1) = f(n-2) + ... + f(1) + f(0),化简可得f(n) = 2 * f(n-1),这就是整个问题的通项公式啦:
于是可以快乐写出代码:
递归版:
class Solution {
public:
int jumpFloorII(int number) {
if (number == 1) {
return 1;
}
else {
return 2 * jumpFloorII(number - 1);
}
}
};
循环版:
class Solution {
public:
int jumpFloorII(int number) {
int num = 1;
for (int i = 1; i < number; i++) {
num *= 2;
}
return num;
}
};
2. 排列组合思想
对于每一级台阶,都有跳或不跳两种选择,而对最后一级台阶,只能选择跳,于是就直接得到了f(n) = 2^(n - 1)
直接return的代码:
class Solution {
public:
int jumpFloorII(int number) {
return pow(2, number - 1);
}
};