算法知识点————背包问题【动态规划】【打家劫舍】【股票买卖】

万能头文件#include<bits/stdc++.h>

01 背包

定义: 物品只能用1次。01对应选还是不选第i个物品 .N个物品、V容量的最大价值。
思路:
(1)f[ i ] [j] 表示前i个物品容量j的最大价值。

(2)当前背包容量不够(j < V[i]),不能选i了,此时最大价值是前i-1和物品的最大价值。f[i] [j] = f[i-1] [j]

(3) 容量够用。可以选i也可以不选i。

选i。f[i] [j] = f[i-1] [j-V[i]] + w[i]

不选i。f[i] [j] = f[i-1] [j]

这两种情况取最值max()

#include<bits/stdc++.h>
using namespace std;
const int MAXX = 1010;
int f[MAXX][MAXX];//f[i][j] 前i个物品容量j的最大价值
int v[MAXX]; // 体积
int w[MAXX]; // 价值
int main(){
  int n,m;
  cin>>n>>m;
  for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      if(j < v[i]){//此时不能装下第i个物品,最优解是前i-1的最优解
        f[i][j] = f[i-1][j];
      }else{
        f[i][j] = max(f[i-1][j],f[i-1][j-v[i]] + w[i]);//此时能装下第i个物品但是选不选i这个物品这两种情况的价值要取最大的
      }
    }
    
  }
  cout<<f[n][m]<<endl;
  return 0;
}

优化:成一维

(1) f[j] 容量j下的最优解

(2)这里的j应该逆序更新

(3) f[j] = max(f[j] , f[j-v[i] ] + w[i] )

#include<bits/stdc++.h>
using namespace std;
const int MAXX = 1010;
int f[MAXX];//f[j] 容量j的最大价值
int v[MAXX]; // 体积
int w[MAXX]; // 价值
int main(){
  int n,m;
  cin>>n>>m;
  for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
  for(int i=1;i<=n;i++){
    //for(int j=m;j>=0;j--){
    for(int j=m;j>= v[i];j--){
      //if(j < v[i]){//此时不能装下第i个物品,最优解是前i-1的最优解
       // f[j] = f[j];
     // }else{
        f[j] = max(f[j],f[j-v[i]] + w[i]);//此时能装下第i个物品但是选不选i这个物品这两种情况的价值要取最大的
     // }
    }
    
  }
  cout<<f[m]<<endl;
  return 0;
}

优化输入

#include<bits/stdc++.h>
using namespace std;
const int MAXX = 1010;
int f[MAXX];//f[j] 容量j的最大价值
int main(){
  int n,m;
  cin>>n>>m;
 // for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
  for(int i=1;i<=n;i++){
    int v,w;
    cin>>v>>w;//一边输入一边处理
    for(int j=m;j>= v;j--){
        f[j] = max(f[j],f[j-v] + w);//此时能装下第i个物品但是选不选i这个物品这两种情况的价值要取最大的
    }
  }
  cout<<f[m]<<endl;
  return 0;
}

完全背包问题

定义:每种物品都有无限件可用。

一维结论是for(int j=m;j>= v[i];j–){这里面是正向for(int j=v[i] ;j<=m;j++)

01背包区别:f[i] [j] = max(f[i-1] [j] , f[i-1] [j-v[i]] +w[i])

完全背包区别:f[i] [j] = max(f[i-1] [j] ,f[i] [j-v[i]] +w[i])

#include<bits/stdc++.h>
using namespace std;
const int MAXX = 1010;
int f[MAXX];
int v[MAXX];
int w[MAXX];
int main(){
  int n,m;
  cin>>n>>m;
  for(int i=0; i<n; i++){
    cin>>v[i]>>w[i];
  }
  for(int i=0;i<n;i++){
    for(int j=v[i];j<=m;j++){//这里正序
      f[j] = max(f[j],f[j-v[i]] + w[i]);
    }
  }
  cout<<f[m]<<endl;
  return 0;
}

在这里插入图片描述

动态规划基础版本(空间优化版本)题解相似

题目 :爬楼梯(斐波那契数)

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶

思路: 要么在n-1个台阶上,结果是n-1的方案数,要么要么在n-1个台阶上,结果 是n-2的方案数;两个方案数量和就是结果;f1是上一个状态 f0是上上一个状态
在这里插入图片描述

class Solution {
public:
    int climbStairs(int n) {
        int f0 =1, f1 = 1;
        for(int i = 2; i<= n ;i ++){
            int  f_res = f1 + f0;
            f0 = f1;
            f1 = f_res;
        }
        return f1;
    }
};
题目 :使用最小花费爬楼梯

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
示例 1:
输入:cost = [1,100,1,1,1,100,1,1,100,1]
输出:6
解释:你将从下标为 0 的台阶开始。

  • 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
  • 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
  • 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
  • 支付 1 ,向上爬一个台阶,到达楼梯顶部。
    总花费为 6 。
    思路:
    //fn = min( fn-1 | fn-2 + cost[n] )
    //要解决的问题:dfs(i) 0或1 爬到i的最小花费
    //枚举分类:
    // 最后爬1个dfs(i) = dfs(i-1) +cost[i-1]
    // 最后爬2个 dfs(i) = dfs(i-2) +cost[i-2]
    //边界 dfs(0) =0或dfs(1) =0 爬到0或1 无需花费

//f1:上一个状态
//f0:上上一个状态
//新的状态newF = min(f1 + cost[i-1],f0 + cost[i-2])

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int f0 = 0,f1 = 0;
        int len = cost.size();
        if(len == 0 | len == 1) return 0;
        
        for(int i=2; i <= len; i++){
            int newF = min(f1 + cost[i-1] ,f0 + cost[i-2]);
            f0 = f1;
            f1 = newF;
        }
        return f1;
    }
};
题目 :打家劫舍

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下(如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。) ,一夜之内能够偷窃到的最高金额。
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

class Solution {
public:
    int rob(vector<int>& nums) {
        int f0 = 0,f1 = 0;//只有一家的时候不能偷
        
        for(auto num : nums){
            int newF = max(f1 , f0 + num);
            f0 = f1;
            f1 = newF;
        }
        return f1;
    }
};
//最后一个选不选的问题
//fn 最高金额 = max (f1  | f0 + nums[n])
//上一个状态 f1 
//上上状态 f0
打家劫舍II

思路:如果偷 nums[0],那么 nums[1] 和 nums[n−1] 不能偷,问题变成从 nums[2] 到 nums[n−2] 的非环形版本,调用 打家劫舍的代码解决;
如果不偷 nums[0],那么问题变成从 nums[1] 到 nums[n−1] 的非环形版本,同样调用 打家劫舍的代码解决

class Solution {
public:
    int rob1(vector<int>& nums ,int start,int end){
        int f0 = 0,f1 = 0;
        for(int i = start;i < end; i++){
            int newf = max(nums[i] + f0 , f1);
            f0 = f1 ;
            f1 = newf;
        }
        return f1;
    }

    int rob(vector<int>& nums) {
        int n = nums.size();
        return max(nums[0] + rob1(nums,2,n-1), rob1(nums,1,n));
    }
};
打家劫舍IV

对树进行打家劫舍,优先考虑叶子结点因为叶子结点限制比较少,那么就对应后序遍历树,在遍历根的时候处理根,可以选择偷根和不偷根,对应两个哈希表,记录偷和不偷的时候对应的价值,当偷根的时候,孩子不能偷,所以是根的值加上左孩子没偷和右孩子没有偷的值。
当没有偷根的时候,孩子偷不偷不一定,要取左子树偷和左子树没偷的最大值,同理和右子树偷和右子树没偷的最大值。
主函数:遍历树和返回根偷和没偷的最值

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    unordered_map<TreeNode*, int> f, g; // f 偷 g 不偷
    void dfs(TreeNode* node) {
        if (!node)
            return;
        dfs(node->left);
        dfs(node->right);
        // 处理根
        f[node] =
            node->val + g[node->left] + g[node->right]; // 根偷加上孩子没有偷
        g[node] =
            max(f[node->left], g[node->left]) +
            max(f[node->right],g[node->right]); // 根没偷:左子树偷和左子树没偷的最大值+右子树偷和右子树没偷的最大值
    }
    int rob(TreeNode* root) {
        dfs(root);//后续遍历
        return max(f[root],g[root]);//返回偷还是没偷根的最值
    }
};
题目 :删除并获得点数

给你一个整数数组 nums ,你可以对它进行一些操作。
每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。
开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。
示例 1:
输入:nums = [3,4,2]
输出:6
解释:
删除 4 获得 4 个点数,因此 3 也被删除。
之后,删除 2 获得 2 个点数。总共获得 6 个点数。
示例 2:
输入:nums = [2,2,3,3,3,4]
输出:9
解释:
删除 3 获得 3 个点数,接着要删除两个 2 和 4 。
之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。
总共获得 9 个点数。

**思路:*重点在于题目的转化,此题相当于对哈希表进行打家劫舍,其中的哈希表里面存的是数组里面数字出现的次数 数字(也就是累加)

//哈希表长度最大10^4 +1 (这里面可以直接设置MAXN 也可以用nums的最大值)
//哈希表存储累计 的 数字个数*下标
//然后对哈希表进行打家劫舍 就能求出最大点数量
(这题一开始运行时间有点长)因为用了哈希map,后来想想思想都一样map换成vector,果然运行时间缩短了。。。。
在这里插入图片描述

class Solution {
public:
    int deleteAndEarn(vector<int>& nums) {
        int maxValue = 0;
        for(int i=0; i < nums.size(); i++){
            maxValue = max(maxValue,nums[i]);
        }
        vector<int> v1(maxValue+1);
        for(int i=0; i < nums.size(); i++){
            v1[nums[i]] += nums[i];
        }
        //打家劫舍
        int f0 = 0,f1 = 0;
        int newF = f0+f1;
        for(auto num : v1){
            newF = max(f0 + num, f1);
            f0 = f1;
            f1 = newF;
        }
        return f1;
    }
};

路径问题(矩阵DP)

维护两个二维向量
一个是原始的路径矩阵,一个是状态方程矩阵dp[i][j] = dp[i-1][j] + dp[i][j-1];
dp[i][j] 表示到达点i,j位置的路径有多少条。
第一题情况是原始路径矩阵对应值都是1表示可以走,第二个题原始路径矩阵的值表示大小,这个时候状态方程dp[i][j]可以表示走的路径的和的最小值,第三个题是有障碍物,需要判断 if() = 0 表示可以走

股票买卖系列 I~IV

股票买卖系列://0表示持有 1 表示未持有

​ 持有股票 dp[i] [0]:继承dp[i-1] [0]、刚买的:-prices[i]
​ 没有持有股票dp[i] [1]:昨天就没有一直没买呢dp[i-1] [1],,刚卖了dp[i-1] [0] + prices[i]
​ 每一天都需要考虑持有和没有的状态;只允许买卖一次;卖在买之后;(二维数组 2*n)
在这里插入图片描述
传统思路解法:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        if(len <= 1) return 0;
        vector<vector<int>> dp(len,vector<int>(2));
        dp[0][0] = -prices[0]; //0表示持有 1 表示未持有 dp[0][0] 表示第一天持有,说明第一天就买了,所以现金是-prices[0]
        dp[0][1] = 0; //dp[0][1] 表示第一天没有持有
        for(int i = 1;i<len;i++){
            dp[i][0] =  max(dp[i-1][0] ,-prices[i]);
            //第i天持有股票的价格的可能(前一天就持有了的价格,或者今天刚买的价格,二者取最大)
            dp[i][1] = max(dp[i-1][1],dp[i-1][0] + prices[i]);
            //第i天没有持有股票的价格(前一天也没有持有,或者卖出了,价格加prices[i],二者取最大)
        }
        
        return dp[len-1][1];
       
    }
};

可以多次买入卖出,但是手里股票最多只能有一个,说明dp[ i ] [ 0 ] = max(dp[i-1] [0],dp[i-1] [1] - prices[i]); 中的买入依赖于前一个状态的价格所以➕➖prices的时候以前一个状态为基础进行加减。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        vector<vector<int>> dp(len,vector<int>(2));
        dp[0][0] = -prices[0] ;//0持有
        dp[0][1] = 0;
        for(int i = 1 ;i < len;i++){
            dp[i][0] = max(dp[i-1][0],dp[i-1][1] - prices[i]);
            dp[i][1] = max(dp[i-1][1],dp[i-1][0] + prices[i]);
        }
        return dp[len-1][1];
    }
};

优化成一维的
既然一直都依赖于前一个的状态可以将dp[0] [0] 变成dp0表示前一个状态持有,dp[0] [1]变成dp1表示前一个状态没有持有

dp[i] [0] = max(dp[i-1] [0],dp[i-1] [1] - prices[i]);—>int newdp0 = max(dp0,dp1-prices[i])
dp[i] [1] = max(dp[i-1] [1],dp[i-1] [0] + prices[i]);---->int newdp1 = max(dp1,dp0+prices[i])
dp0 = newdp0;
dp1 =newdp1;

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        int  dp0 = -prices[0] ;//0持有
        int  dp1 = 0;
        for(int i = 1 ;i < len;i++){
            int newdp0 = max(dp0,dp1 - prices[i]);
            int newdp1 = max(dp1,dp0 + prices[i]);
            dp0 = newdp0;
            dp1 = newdp1;
        }
        return dp1;
    }
};

手里最多有2两个股票
传统版本到一维版本,可以在传统版本上面进行改变

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        if(len <= 1) return 0;
        vector<vector<int>> dp(len,vector<int>(4));
        //dp[0][0] = -prices[0];//0持有
        int dp0 = -prices[0];
        //dp[0][1] = 0;
        int dp1 = 0 , dp3 = 0;
        //dp[0][2] = -prices[0];//0持有
        int dp2 = -prices[0];
        for(int i = 1;i < len ;i++){
           // dp[i][0] = max(dp[i-1][0], - prices[i]);
            //dp[i][1] = max(dp[i-1][1],dp[i-1][0] + prices[i]);
            //dp[i][2] = max(dp[i-1][2],dp[i-1][1] - prices[i]);
            //dp[i][3] = max(dp[i-1][3],dp[i-1][2] + prices[i]);
            int newdp0 = max(dp0,-prices[i]);
            int newdp1 = max(dp1,dp0+prices[i]);
            int newdp2 = max(dp2,dp1-prices[i]);
            int newdp3 = max(dp3,dp2+prices[i]);
            dp0 = newdp0;
            dp1 = newdp1;
            dp2 = newdp2;
            dp3 = newdp3;
        }
        //return dp[len-1][3];
        return dp3;
    }
};

最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。

总共就len个数,买卖的次数和小于len的个数
1次 、2 次。。。。k次 对应2k次状态
出来第一次不一样,后面的都一样可以用奇偶判断
在这里插入图片描述
传统写法

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int len = prices.size();
        if(len <= 1) return 0;
        //更新k
        k = min(k,len/2);
        vector<vector<int>> dp(len,vector<int>(2*k));
        //初始化
        for(int i = 0; i < 2*k;i++){
            if(i%2 == 0) dp[0][i] = -prices[0];
            else dp[0][i] = 0;
        }
        for(int i = 1; i < len;i++){
            for(int j = 0;j < 2*k ;j++){
                if(j == 0){//第一次单独写
                    dp[i][j] = max(dp[i-1][0], -prices[i]);
                }else{//后面有规律
                    if(j % 2 != 0)
                        dp[i][j] = max(dp[i-1][j],dp[i-1][j-1] + prices[i]);
                    else
                        dp[i][j] = max(dp[i-1][j],dp[i-1][j-1] - prices[i]);
                }
            }
        }
        return dp[len-1][2 * k - 1];
    }
};

优化成一维;直接写一维比较难思考,直接在传统的写法上面进行修改更容易理解。

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int len = prices.size();
        if(len <= 1) return 0;
        //更新k
        k = min(k,len/2);
        vector<vector<int>> dp(len,vector<int>(2*k));
        vector<int> dp0(2*k);
        for(int i = 0; i < 2*k;i++){
            //if(i%2 == 0) dp[0][i] = -prices[0];
           // else dp[0][i] = 0;
            if(i%2 == 0) dp0[i] = -prices[0];
            else dp0[i] = 0;
        }
        vector<int> newdp(2*k);
        for(int i = 1; i < len;i++){
            for(int j = 0;j < 2*k ;j++){
                if(j == 0){
                    //dp[i][j] = max(dp[i-1][0], -prices[i]);
                    newdp[j] = max(dp0[0],-prices[i]);
                }else{
                    if(j % 2 != 0)
                       // dp[i][j] = max(dp[i-1][j],dp[i-1][j-1] + prices[i]);
                        newdp[j] = max(dp0[j],dp0[j-1] + prices[i]);
                    else
                        //dp[i][j] = max(dp[i-1][j],dp[i-1][j-1] - prices[i]);
                        newdp[j] = max(dp0[j],dp0[j-1] - prices[i]);
                }
                dp0[j] = newdp[j];
            }
        }
        //return dp[len-1][2 * k - 1];
        return dp0[2 * k -1];
    }
};

在进一步优化 :注意 newdp可以用dp0替换,原因是没有使用的状态dp0使用完之后可以赋值给dp0表示新的状态,newdp[j]只是为了计算的中间的变量。

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int len = prices.size();
        if(len <= 1) return 0;
        //更新k
        k = min(k,len/2);
        vector<vector<int>> dp(len,vector<int>(2*k));
        vector<int> dp0(2*k);
        for(int i = 0; i < 2*k;i++){
            if(i%2 == 0) dp0[i] = -prices[0];
            else dp0[i] = 0;
        }
        for(int i = 1; i < len;i++){
            for(int j = 0;j < 2*k ;j++){
                if(j == 0){
                    dp0[j] = max(dp0[0],-prices[i]);
                }else{
                    if(j % 2 != 0)
                        dp0[j] = max(dp0[j],dp0[j-1] + prices[i]);
                    else
                        dp0[j] = max(dp0[j],dp0[j-1] - prices[i]);
                }              
            }
        }
        return dp0[2 * k -1];
    }
};

题目:买卖股票的最佳时机含手续费

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int len = prices.size();
        if(len <= 1) return 0;
        int dp0 = -prices[0];
        int dp1 = 0;
        for(int i = 1;i < len ; i++){
            int newdp0 = max(dp0,dp1 - prices[i]);
            int newdp1 = max(dp1,dp0 + prices[i] - fee); //这里卖出的时候减去手续费
            dp0 = newdp0;
            dp1 = newdp1;
        }
        return dp1;
    }
};

题目:买卖股票含冷冻期

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        if(len <= 1)  return 0;
        int dp0 = -prices[0];
        int dp1 = 0;
        int dp2 = 0;
        for(int i = 1 ; i < len ;i++){
            int newdp0 = max(dp0 , dp2 - prices[i]);//dp1前一天没持有dp0前一天持有dp2前一天的冷冻期
            int newdp1 = max(dp1 ,dp0 + prices[i]); 
            dp2 = dp1;
            dp0 = newdp0;
            dp1 = newdp1;
        }
        return dp1;
    }
};

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shan_shmily

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值