关于斐波拉契数列:
LeetCode509
斐波那契数,通常用 F(n)
表示,形成的序列称为斐波那契数列。该数列由 0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
给定 N
,计算 F(N)
。
上述斐波拉契数列是一个很经典的问题
对于该问题的解法,首先可以使用递归实现该问题的求解
class Solution {
public:
int fib(int N) {
if(N == 0)
return 0;
if(N == 1)
return 1;
return fib(N-1) + fib(N-2);
}
};
分析:上面程序,在计算每一项时,都要分别计算前面每一项,比如说在计算F(3)的时候,已经计算了F(2)和F(1),但是计算后面的每一项都会进行这个计算,这样有很多重复子问题,该算法的时间复杂度相当高
思考:既然每一项都能求过,那么我们把每一个子问题的解都求解出来,存放到数组里,我们在求解F(n-1) + F(n-2)时,直接去搜索F(n-1)和F(n-2)是否已经求解过,再进行递归
class Solution {
public:
int Fibolaqi(int n, vector<int> &hh)
{
if(n == 0)
return 0;
if(n == 1)
return 1;
if(hh[n] == -1)
hh[n] = Fibolaqi(n-1, hh) + Fibolaqi(n-2, hh);
return hh[n];
}
int fib(int n)
{
vector<int> hh(n+1, -1);
return Fibolaqi(n, hh);
}
};
分析:这种自上向下的记忆式搜索相对于递归就已经优化了很多了
思考:既然我们递归可以记忆式搜索,那为什么我们不可以不进行递归,直接一次将斐波拉契数列的每一项求出来存到数列里。
class Solution {
public:
int fib(int N) {
vector<int> res;
if(N == 0)
return 0;
if(N == 1)
return 1;
res = {0,1};
for(int i = 2;i <= N;i++){
int xx = res[i -1] + res[i - 2];
res.push_back(xx);
}
return res[N];
}
};
上述代码中,把多阶段问题化解成多个单阶段问题,然后根据各阶段之间的关系求解问题,就是动态规划。
下面列举几个LeetCode上类似于上述问题的几个问题,及个人愚解:
LeetCode120:三角形最小路径和
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11
(即,2 + 3 + 5 + 1 = 11)。
思路:每一个点到最下面的最短路径等于自身加上下一层左邻的最短和右邻最短的最小值,这样,就可以从下往上的求解出每一个的最小值,从而得到第一个的值。
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int size = triangle.size();
//将结果数组初始化为最后一行
vector<int> hh = triangle[size - 1];
//从下往上开始算每一个
for(int i = size - 2; i >= 0; i--)
//三角形,j<i
for(int j = 0; j <= i; j++)
hh[j] = triangle[i][j] + min(hh[j], hh[j + 1]);
return hh[0];
}
private:
int min(int a, int b)
{
if(a <= b)
return a;
else
return b;
}
};
LeetCode64:最小路径和
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
思路:到达每一步的最短路径就是自身加上左和上的最小值,这样就可以逐步求得最优解。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
vector<vector<int>> hh = grid;
for(int k = 1; k < grid.size(); k++)
hh[k][0] = hh[k - 1][0] + grid[k][0];
for(int l = 1; l < grid[0].size(); l++)
hh[0][l] = hh[0][l - 1] + grid[0][l];
for(int i = 1; i < grid.size(); i++)
{
for(int j = 1; j < grid[0].size(); j++)
{
hh[i][j] = grid[i][j] + min(hh[i - 1][j], hh[i][j - 1]);
}
}
return hh[grid.size() - 1][grid[0].size() - 1];
}
};
最优子结构:
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
说明: 你可以假设 n 不小于 2 且不大于 58。
思考:
求解分解n的最优解,可以分解为求解若干最优子解,且很多都是重复操作,此时就可以使用动态规划,将分解2,3,....n-1的最优解求解出来,并存到数组里,就可以得到n的最优解:
class Solution {
public:
int integerBreak(int n) {
vector<int> best_everyone(n + 1, -1);
//n>=2 用a[i]代表i分解的最优解
best_everyone[1] = 1;
best_everyone[2] = 1;
for(int i = 3; i <= n; i++)//从3开始求解
{
for(int j = 1; j <= i; j++)//将i分解
{
best_everyone[i] = max(j * best_everyone[i - j], max(best_everyone[i], j * (i - j)));
}
}
return best_everyone[n];
}
};
下面介绍几道同类型的题目:
LeetCode279:279. 完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...
)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n =12
输出: 3 解释:12 = 4 + 4 + 4.
示例 2:
输入: n =13
输出: 2 解释:13 = 4 + 9.
思路:从下图可以看到,当两个数的差等于一个完全平方数,那么这两个点可以相连,这样就成了求解一个图的最短路径问题了。
class Solution {
public:
int numSquares(int n) {
//创建一个存储每个数字的最小路径
if(n == 1)
return 1;
if(n == 2)
return 2;
int isSquares[n];
for(int i = 0; i < n; i++)
isSquares[i] = isSquare(i + 1);
vector<int> min_i(n + 1, 0);
min_i[1] = 1;
min_i[2] = 2;
for(int i = 3; i <= n; i++)//我就要用下标来代表第几
{
//cout << "hh" << i << endl;
//一个点的最短,等于他能到达的点中最小的
if(isSquares[i - 1])
{
min_i[i] = 1;
}
else
{
int min = 1 + min_i[i - 1];
for(int j = 1; j < i; j++)
{
if(isSquares[j - 1])
{
if(min > 1 + min_i[i - j])
min = 1 + min_i[i - j];
}
}
min_i[i] = min;
}
//cout << min_i[i] << endl;
}
return min_i[n];
}
private:
bool isSquare(int m){
double x = sqrt(m);
if (floor(x) == x) {
return true;
}
return false;
}
};
上述代码不知道是我啥时候写的,然后运行始终超时。为什么会超时呢?