斐波那契数列
///=动态规划---自底向上
int dongtaiguihua(int n){
vector<int> v_m(n+1, -1);
v_m[0] = 0;
v_m[1] = 1;
if(n <= 0){
return 0;
}
for(int i = 2; i <= n; i++){
v_m[i] = v_m[i-1] + v_m[i-2];
}
return v_m[n];
}
动态规划:
LeetCode70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 2:
输入: 3 输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
思路:求爬n阶台阶的方法数,等于两种情况,①先爬n-1阶再爬1阶和②先爬n-2阶再爬2阶,即F(n-1) + F(n-2)。
///==方法1.记忆化搜索
class Solution {
private:
vector<int> v_way;
int calcuWay(int n){
if(n == 0 || n == 1)
return 1;
if(v_way[n] == -1)
v_way[n] = calcuWay(n-1) + calcuWay(n-2);
return v_way[n];
}
public:
int climbStairs(int n) {
v_way.reserve(n+1);
v_way = vector<int>(n+1, -1);
return calcuWay(n);
}
};
///==方法2.动态规划
///爬n阶的方法数等于爬n-1阶的方法数+爬n-2阶的方法数
class Solution2 {
public:
int climbStairs(int n) {
vector<int> v_way(n+1, -1);
v_way[0] = 1;
v_way[1] = 1;
for(int i = 2; i <= n; i++){
v_way[i] = v_way[i-1] + v_way[i-2]; //核心关键.动态方程
}
return v_way[n];
}
};
LeetCode198:偷盗房屋
//Time Complexity: O(n) Space Complexity: O(n)
class Solution {
public:
int rob(vector<int>& nums) {
vector<int> v_memo(nums.size(), -1);
int n = nums.size();
if(n == 1){
v_memo[0] = nums[0];
return v_memo[0];
}
v_memo[0] = nums[0];
v_memo[1] = max(nums[0], nums[1]);
///===此题动态规划就是计算当为n时,v_memo[n-1]和 v_memo[n-2]+nums[n] 谁更大。
///===v_memo[i]表示为前i间房偷取的最大金额,nums[i]表示第i间房中的金钱数
for(int i = 2; i < n; i++){
v_memo[i] = max(v_memo[i - 1], nums[i] + v_memo[i - 2]); //状态方程
}
return v_memo[n-1];
}
};
优化版:
上述方法使用了数组存储结果。考虑到每间房屋的最高总金额只和该房屋的前两间房屋的最高总金额相关,因此可以使用滚动数组,在每个时刻只需要存储前两间房屋的最高总金额。
复杂度分析
时间复杂度:O(n),其中 n 是数组长度。只需要对数组遍历一次。
空间复杂度:O(1)。使用滚动数组,可以只存储前两间房屋的最高总金额,而不需要存储整个数组的结果,因此空间复杂度是 O(1)。
//优化版
//Time Complexity: O(n) Space Complexity: O(1)
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
int curMax = 0, preMax = 0;
if(n == 1)
return nums[0];
//因为动态规划在比较时,只需要用到n-1 和 n-2时的最大值,所以这里没必要建立一个数组去保存中间结果
//仅需要两个变量保存n-1和n-2的结果即可
preMax = nums[0]; //前n-2时的最大值
curMax = max(nums[0], nums[1]); //前n-1的最大值
///===此题动态规划就是计算当为n时,v_memo[n-1]和 v_memo[n-2]+nums[n] 谁更大。
///===v_memo[i]表示为前i间房偷取的最大金额,nums[i]表示第i间房中的金钱数
int temp = 0;
for(int i = 2; i < n; i++){
temp = curMax;
curMax = max(curMax, nums[i] + preMax); //状态方程
preMax = temp;
}
return curMax;
}
};
LeetCode213:偷盗房屋II
class Solution {
private:
int Dynamic(vector<int>& nums, int idexStart, int idexEnd){
if(idexEnd == idexStart)
return nums[idexStart];
if((idexEnd - idexStart + 1) == 2)
return max(nums[idexStart], nums[idexEnd]);
int preMax = nums[idexStart];
int curMax = max(nums[idexStart], nums[idexStart + 1]);
for(int i = idexStart + 2; i <= idexEnd; i++){
int temp = curMax;
curMax = max(curMax, preMax + nums[i]);
preMax = temp;
}
return curMax;
}
public:
int rob(vector<int>& nums) {
if(nums.size() == 1)
return nums[0];
if(nums.size() == 2)
return max(nums[0], nums[1]);
int n = nums.size() - 1;
///首尾不能同时偷,所以拆分求[0,n-1]和[1,n]这两个区间的最大金额,再进行比较
int res_1 = Dynamic(nums, 0, n - 1); // [0,n-1]
int res_2 = Dynamic(nums, 1, n); // [1,n]
return max(res_1, res_2);
}
};
01背包问题
动态方程:(如何分析出动态方程是关键)
推理流程:从i---->n-1依次动态推理。下面两个剪头的的起点就是动态方程的两个比较值。
代码1:时间复杂度O(nC) 空间复杂度O(nC)
先欠着
代码2:时间复杂度O(nC) 空间复杂度O(2C)
分析:因为在动态规划推理时,我们每次只需要保存当前行和前一行的内容,所以内存不需要开辟O(nC),只需要开辟O(2C)即可,当n很大时,能够节省很多空间
class Solution {
public:
int knapsack01(const vector<int> &W,const vector<int> &V,int C) {
if (W.size() != V.size())
return 0;
int n = W.size();
if (n == 0)
return 0;
///因为动态规划在比较时,只需要对比前一行的结果,所以我们只需要存储当前行和前一行的内容就行,
/// 因此空间复杂度由O(nC)变为O(2C)
vector<vector<int>> v_memo(2, vector<int>(C + 1, 0));
///通过i%2的方式,循环将结果保存到两行中
for (int i = 0; i <= C; i++)
v_memo[i%2][i] = i >= W[0] ? V[0] : 0;
for (int i = 1; i < n; i++){
for (int j = 0; j <= C; j++) {
if (j > W[i])
v_memo[i % 2][j] = max(v_memo[(i - 1) % 2][j], V[i] + v_memo[(i - 1) % 2][j - W[i]]);
else
v_memo[i % 2][j] = v_memo[(i - 1) % 2][j];
}
}
return v_memo[(n-1)%2][C];
}
};
代码3:时间复杂度O(n) 空间复杂度O(C)
分析:这里的操作就极其炫酷了,我们发现在计算动态方程时,只需要用到当前位置的左方和上方,所以可以把结果全部弄到一行来保存,此时,空间复杂度就降低为O(C)了。
class Solution2 {
public:
int knapsack01(const vector<int> &W,const vector<int> &V,int C) {
if (W.size() != V.size())
return 0;
int n = W.size();
if (n == 0)
return 0;
/// 终极大升级,只利用一行存储空间
/// 因此空间复杂度由O(nC)变为O(C)
vector<int> v_memo( + 1, 0);
for (int i = 0; i <= C; i++)
v_memo[i] = i >= W[0] ? V[0] : 0;
for (int i = 1; i < n; i++){
for (int j = C; j >= 0; j--) { //从右往左推理,当前j的左边时前一行的结果,j的右边则是当前行的结果
if (j > W[i])
v_memo[j] = max(v_memo[j], V[i] + v_memo[j - W[i]]);
}
}
return v_memo[C];
}
};
LeetCode300:最长上升子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
"动态规划方法:时间O(n^2) 空间O(n)"
"还有更快的方法:贪心+二分法 o(nlogn).这里暂不学习."
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() == 0)
return 0;
vector<int> v_memo(nums.size(), 1);
for(int i = 0; i < nums.size(); i++){
for(int j = 0; j < i; j++){
if(nums[i] > nums[j]){
v_memo[i] = max(v_memo[i], v_memo[j] + 1);
}
}
}
int res = 1;
for(int i = 0; i < v_memo.size(); i++)
res = max(res, v_memo[i]);
return res;
}
};