动态规划类型题目汇总及解析(持续更新)

目录

数字三角形模型

摘花生 

最低通行费

方格取数(洛谷)

传纸条(洛谷) 

最长上升子序列模型

最长上升子序列(洛谷)&最长递增子序列(leetcode)

leetcode674. 最长连续递增序列 

 leetcode718. 最长重复子数组

怪盗基德的帽子 

登山

合唱队形(洛谷) 

友好城市(洛谷) 

 最大上升子序列和 

背包问题分析:

01背包: 

 Leetcode 416. 分割等和子集

 Leetcode 1049. 最后一块石头的重量 II

 Leetcode 494. 目标和

  Leetcode 474.一和零 

完全背包:

Leetcde 518. 零钱兑换 II

 Leetcode 377.组合总数Ⅳ

leetcode 322.零钱兑换

   leetcode279.完全平方数

 leetcode129.单词拆分

多重背包:

多重背包看做01背包

多重背包二进制优化

 单调队列优化

状态机

 leetcode089.打家劫舍

  leetcode买股票的最佳时机4

leetcode121. 买卖股票的最佳时机

leetcode122. 买卖股票的最佳时机 2

leetcode123. 买卖股票的最佳时机 3

leetcode买卖股票的最佳时机含冷冻期

leetcode买卖股票的最佳时机含手续费

树形dp

leetcode 打家劫舍3

 leetcode二叉树的直径

 leetcode二叉树中的最大路径和


持续更新

数字三角形模型

摘花生 

#include<iostream>
using namespace std;
const int N=110;

int r,c;
int T;
int w[N][N],res[N][N];
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&r,&c);
        for(int i=1;i<=r;i++)
            for(int j=1;j<=c;j++)
                scanf("%d",&w[i][j]);

       for(int i=1;i<=r;i++)
           for(int j=1;j<=c;j++)
               res[i][j]=max(res[i-1][j]+w[i][j],res[i][j-1]+w[i][j]);

       cout<<res[r][c]<<endl;
    }

    return 0;
};

最低通行费

注意min的问题一定要考虑边界问题

#include<iostream>
#define INF 1e9;

using namespace std;
const int N=110;

int r;
int w[N][N],res[N][N];
int main(){

    scanf("%d",&r);
    for(int i=1;i<=r;i++)
        for(int j=1;j<=r;j++)
            scanf("%d",&w[i][j]);
            
   
    for(int i=1;i<=r;i++){
        for(int j=1;j<=r;j++){
            if(i==1&&j==1)  res[i][j]=w[i][j];//不能直接写res[1][1]=w[1][1],因为for循环里面有res[1][1]=INF;
            else{
            res[i][j]=INF;//
            if(i>1) res[i][j]=min(res[i][j],res[i-1][j]+w[i][j]);// 只有不在第一行的时候,才可以从上面过来
            if(j>1) res[i][j]=min(res[i][j],res[i][j-1]+w[i][j]);// 只有不在第一列的时候,才可以从左面过来
            }
        }
    }
    cout<<res[r][r]<<endl;


    return 0;
};

方格取数(洛谷)

[NOIP2000 提高组] 方格取数 - 洛谷

#include<iostream>

using namespace std;
const int N=15;

int n;
int w[N][N];
int f[N*2][N][N];
int main(){

    scanf("%d",&n);
    int x,y,r;
    while(1){
        scanf("%d%d%d",&x,&y,&r);
        if(x==0&&y==0&r==0) break;
        w[x][y]=r;
    }
    for(int k=2;k<=n+n;k++){
        for(int i1=1;i1<=n;i1++){
            for(int i2=1;i2<=n;i2++){
                int j1=k-i1,j2=k-i2;
                if(j1>=1&&j1<=n&&j2>=1&&j2<=n){
                    int t=w[i1][j1];
                    if(i1!=i2) t+=w[i2][j2];
                    int &x = f[k][i1][i2];
                    x = max(x, f[k - 1][i1 - 1][i2 - 1] + t);
                    x = max(x, f[k - 1][i1 - 1][i2] + t);
                    x = max(x, f[k - 1][i1][i2 - 1] + t);
                    x = max(x, f[k - 1][i1][i2] + t);

                }

            }
        }
    }
    printf("%d\n", f[n + n][n][n]);



    return 0;
};

传纸条(洛谷) 

 [NOIP2008 提高组] 传纸条 - 洛谷

注意:此题是正方形,所以i1,i2的限制只有1<=i1,i2<=n。下面一道题变成长方形(m*n)的时候,就会有限制:

1\leqslant i1 \leqslant n \quad 1\leqslant k-i1\leqslant m\Rightarrow \quad \left\{\begin{matrix} 1\leqslant i1 \leqslant n\\ k-m\leqslant i1 \leqslant k-1 \end{matrix}\right.

summary \Rightarrow max(1,k-m)\leqslant i1 \leqslant min(n,k-1)

同理,i2范围与i1一致

#include <iostream>
using namespace std;
const int N=60;
int f[N*2][N][N];
int w[N][N];
int m,n;
int main(){
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
            scanf("%d",&w[i][j]);

    for(int k=2;k<=m+n;k++){
        for(int i1=max(1,k-n);i1<=min(k-1,m);i1++){
            for(int i2=max(1,k-n);i2<=min(k-1,m);i2++){
                int j1=k-i1,j2=k-i2;
                int t=w[i1][j1];
                if(i1!=i2) t+=w[i2][j2];
                int &x=f[k][i1][i2];
                x=max(x,f[k-1][i1-1][i2]+t);
                x=max(x,f[k-1][i1][i2-1]+t);
                x=max(x,f[k-1][i1][i2]+t);
                x=max(x,f[k-1][i1-1][i2-1]+t);
            }
        }
    }
    cout<<f[m+n][m][m];//注意两个路线横坐标都是n
    return 0;
}

最长上升子序列模型

最长上升子序列(洛谷)&最长递增子序列(leetcode)

最长上升子序列 - 洛谷

300.最长递增子序列

dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度

状态转移方程:

位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。

所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);

 洛谷题解:

#include <iostream>
using namespace std;
const int N=5050;
int f[N];
int a[N];
int m,n;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++){
        f[i]=1;
        for(int j=1;j<i;j++){
            if(a[j]<a[i]){
                f[i]=max(f[i],f[j]+1);
            }
        }
    }
    int res=0;
    for(int i=1;i<=n;i++) res=max(res,f[i]);
    cout<<res;
    return 0;
}

leetcode题解:

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n=nums.size();
        vector<int> dp(n+1,0);
        for(int i=0;i<n;i++){
            dp[i]=1;
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j]){
                    dp[i]=max(dp[i],dp[j]+1);
                }
            }
        }
        int res=0;
        for(int i=0;i<dp.size();i++){
            res=max(res,dp[i]);
        }
        return res;
   }
};

leetcode674. 最长连续递增序列 

 674. 最长连续递增序列

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
            int n=nums.size();
            vector<int> dp(n+1,0);
            dp[0]=1;
            for(int i=1;i<n;i++){
                if(nums[i]>nums[i-1]){
                    dp[i]=dp[i-1]+1;
                }
                else{
                    dp[i]=1;
                }
            }

             int res=0;
        for(int i=0;i<dp.size();i++){
            res=max(res,dp[i]);
        }
        return res;
    }

};

 leetcode718. 最长重复子数组

最长重复子数组

定义dp[i][j]为 以下标i为结尾的A,和以下标j 为结尾的B,最长重复子数组长度。

确定递推公式:

根据dp[i][j]的定义,dp[i][j]的状态只能由dp[i - 1][j - 1]推导出来。

即当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;

例如:【1,2,3,4】与【9,1,2,3】

当i=1,j=2时候,相等,由于连续,那么i退一格,j退一格,刚好就是dp【i】【j】的更新。

根据递推公式可以看出,遍历i 和 j 要从1开始!

这里代码用:i-1是为了方便,不想初始化,直接把0视为无用位,从1开始推。 

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp (nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
        int result = 0;
        for (int i = 1; i <= nums1.size(); i++) {
            for (int j = 1; j <= nums2.size(); j++) {
                if (nums1[i - 1] == nums2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                if (dp[i][j] > result) result = dp[i][j];
            }
        }
        return result;
    }
};

 

怪盗基德的帽子 

 本质:做了正反两次的LIS(最长上升子序列),进行比较大小,找出最长递增子序列

#include <iostream>
using namespace std;
const int N=110;

int w[N],f[N];
int main(){
    int k;
    cin>>k;
    int n;
    while(k--){
        cin>>n;
        for(int i=1;i<=n;i++) cin>>w[i];
        //正向
        int res=0;
       for(int i=1;i<=n;i++){
           f[i]=1;
           for(int j=1;j<i;j++){
               if(w[j]<w[i]){
                   f[i]=max(f[i],f[j]+1);
               }
           }
           res=max(res,f[i]);
       }

       //反向
       for(int i=n;i>=1;i--){
           f[i]=1;
           for(int j=n;j>i;j--){
               if(w[j]<w[i]){
                   f[i]=max(f[i],f[j]+1);
               }
           }
           res=max(res,f[i]);
       }
        cout<<res<<endl;
    }

    return 0;
}

登山

与上题一模一样,上题是单边找最大,这题是把两边加起来-1就可以,本质一样。

#include <iostream>
using namespace std;
const int N = 1010;

int n;
int h[N];
int f[N], g[N];
int main(){
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", &h[i]);
    for (int i = 0; i < n; i ++ )
    {
        f[i] = 1;
        for (int j = 0; j < i; j ++ )
            if (h[i] > h[j])
                f[i] = max(f[i], f[j] + 1);
    }

    for (int i = n - 1; i >= 0; i -- )
    {
        g[i] = 1;
        for (int j = n - 1; j > i; j -- )
            if (h[i] > h[j])
                g[i] = max(g[i], g[j] + 1);
    }

    int res = 0;
    for (int i = 0; i < n; i ++ ) res = max(res, f[i] + g[i] - 1);

    printf("%d\n", res);
    return 0;
}

合唱队形(洛谷) 

[NOIP2004 提高组] 合唱队形 - 洛谷

 和登山一模一样,但是记得减一就好,模型就是:求前后的最大上升子序列,减去总共的就是。

#include <iostream>
using namespace std;
const int N = 110;

int n;
int h[N];
int f[N], g[N];
int main(){
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &h[i]);
    for (int i = 1; i <=n; i ++ )
    {
        f[i] = 1;
        for (int j = 1; j < i; j ++ )
            if (h[i] > h[j])
                f[i] = max(f[i], f[j] + 1);
    }

    for (int i = n;i; i -- )
    {
        g[i] = 1;
        for (int j = n; j > i; j -- )
            if (h[i] > h[j])
                g[i] = max(g[i], g[j] + 1);
    }

    int res = 0;
    for (int i = 1; i <=n; i ++ ) res = max(res, f[i] + g[i] - 1);
    int k=n-res;
    printf("%d\n", k);
    return 0;
}

友好城市(洛谷) 

友好城市 - 洛谷

 

 if \quad index_1<index_2, val_1<val_2 ,then \: OK

单调上升子序列,只有满足如上要求才可以

所以如果有一个序列排好序,则它一定是递增的,只要知道另一个对应的序列

的最长上升子序列,就是它最大的不重叠数量。

方法与思路:(举个栗子,图示)

 用这样的想法我们能得到如下代码:

#include <iostream>
#include <algorithm>

using namespace std;
const int N = 200010;
int n;
typedef pair<int,int> pii;
pii w[N];
int f[N];


int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        w[i]={x,y};
    }
    int res=0;
    sort(w,w+n);
    for(int i=0;i<n;i++){
        f[i]=1;
        for(int j=0;j<i;j++){
            if(w[j].second<w[i].second){
                f[i]=max(f[i],f[j]+1);
            }
        }
        res=max(res,f[i]);
    }
    printf("%d",res);
    return 0;
}

然后就会发现如下的东西:

 超时:

我刚刚利用动态规划复杂度为O(N^2),我们需要另外找一种方案去优化:

法1:

在我们每处理数列中的一位时,我们都要遍历数组找到值小于当前数中的f值的最大值,再用其加一作为现数字的值。如下,就是我们每一次记录的f【i】的值。


那么,如果我们维护一个取值集合,储存可能的最优解, 就可以优化算法的时间复杂度。


将一个数放入取值集合的条件是什么?如果两个数a、b ,当他们的f值相同,且a<b,那么a对于后来的数来说,显然比b优。(运用了优先队列的思想)。例如:图中3,1 用1一定比用3好,因为更小。

我们用函数图像能更直观理解:

所以我们只需要那么我们储存下对每一个f值来说的最小原数字值,在处理完新数字后将新数字与与其f值相同的数字比较大小,若小于则更新,若大于接在e数组后面。

每次我们都查找第一个比当前数大的位置在哪里,用lower_bound即可(二分法)。

用p做记录,记录尾插次数,也就是最长递增子序列,此时优化为了:O(nlog_n)

#include <iostream>
#include <algorithm>

using namespace std;
const int N = 200010;
int n;
typedef pair<int,int> pii;
pii w[N];

int e[N];
int p;
bool cmp(pii x,pii y){
    return x.first<y.first;
};
int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        w[i]={x,y};
    }

    sort(w,w+n,cmp);
    for(int i=0;i<n;i++){
        if(e[p]<w[i].second) {
            p++;
            e[p]=w[i].second;
        }
        else{
            e[lower_bound(e,e+p,w[i].second)-e]=w[i].second;
        }
    }
    cout<<p<<endl;
    return 0;
}

然后就会得到:

 最大上升子序列和 

 

只是在第一个最长上升子序列上面加上了w【i】 ,几乎没有什么变化。

#include<bits/stdc++.h>
using namespace std;

int n,a[1005],f[1005],ans;

int main()
{
    cin >> n;
    for(int i = 1;i <= n;i++) cin >> a[i];
    for(int i = 1;i <= n;i++)
    {
        f[i] = a[i];
        for(int j = 1;j < i;j++)
            if(a[j] < a[i]) f[i] = max(f[i],f[j] + a[i]);
        ans = max(ans,f[i]);
    }
    cout << ans;
    return 0;
}

背包问题分析:

01背包: 

二维: 

#include<bits/stdc++.h>

using namespace std;

const int MAXN = 1005;
int v[MAXN];    // 体积
int w[MAXN];    // 价值 
int f[MAXN][MAXN];  // f[i][j], j体积下前i个物品的最大价值 

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++)
        {
            //  当前背包容量装不进第i个物品,则价值等于前i-1个物品
            if(j < v[i]) 
                f[i][j] = f[i - 1][j];
            // 能装,需进行决策是否选择第i个物品
            else    
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
        }           

    cout << f[n][m] << endl;

    return 0;
}

 滚动数组一维优化:

#include<bits/stdc++.h>
using namespace std;

int f[1001],n,v,c[1001],w[1001];

int main()
{
	cin >> n >> v;
	for(int i = 1;i <= n;i++) cin >> c[i] >> w[i];
	for(int i = 1;i <= n;i++)
		for(int vv = v;vv >= c[i];vv--)
			f[vv] = max(f[vv],f[vv - c[i]] + w[i]);
	cout << f[v];
	return 0;
}

 Leetcode 416. 分割等和子集

Leetcode 416. 分割等和子集

#include<bits/stdc++.h>
using namespace std;
#include <math.h>
class Solution {
    const int N=20001;
public:
    bool canPartition(vector<int>& nums) {
        int res=0;
        for(int i=0;i<nums.size();i++) res+=nums[i];
        if(res%2!=0) return false;

        int dp[N];

        memset(dp,0,sizeof(dp));

        for(int i=0;i<nums.size();i++){
            for(int j=res/2;j;j--){
                if(j>=nums[i]){
                    dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);
                }else{
                    dp[j]=dp[j];
                }
            }

        }
        return dp[res/2]==res/2;
    }
};

 Leetcode 1049. 最后一块石头的重量 II

Leetcode 1049. 最后一块石头的重量 II

#include<bits/stdc++.h>
using namespace std;
  const int N=15001;
    
class Solution {
  
public:
    int lastStoneWeightII(vector<int>& stones) {
        int dp[N];
        memset(dp,0,sizeof(dp));
        int res=0;
        for(int i=0;i<stones.size();i++) res+=stones[i];
        int r=res/2;
        for(int i=0;i<stones.size();i++){
            for(int j=r;j>=stones[i];j--){
                dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);
            }
        }
        return (res-dp[r])-dp[r];
    }
};

 Leetcode 494. 目标和

Leetcode 494. 目标和

class Solution {

public:
    int findTargetSumWays(vector<int>& nums, int target) {

        int sum=0;
        for(int i=0;i<nums.size();i++) sum+=nums[i];
        int z=(sum+target);
        if(sum<abs(target)||z%2!=0) return 0;
        int pos=z/2;
        vector<int> dp(pos + 1, 0);
        dp[0]=1;
        for(int i=0;i<nums.size();i++){
            for(int j=pos;j>=nums[i];j--){
                dp[j]+=dp[j-nums[i]];
            }
        }
     return dp[pos];
    }
};

  Leetcode 474.一和零 

 Leetcode 474.一和零

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>> dp(m + 1, vector<int> (n + 1, 0)); 
        for (string str : strs) { // 遍历物品
            int oneNum = 0, zeroNum = 0;
            for (char c : str) {
                if (c == '0') zeroNum++;
                else oneNum++;
            }
            for (int i = m; i >= zeroNum; i--) {
                for (int j = n; j >= oneNum; j--) {
                    dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
                }
            }
        }
        return dp[m][n];
    }
};

完全背包:


 

优化:

 

总结:

f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);//01背包

f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);//完全背包问题

 二维:

#include<iostream>
using namespace std;

const int N = 10001;

int n, m;
int f[N][N], v[N], w[N];

int main()
{
    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 = 0; j <= m; j ++ )
        {
            if(v[i] <= j)
                f[i][j] =max(f[i - 1][j], f[i][j - v[i]] + w[i]);
            else
                f[i][j] = f[i - 1][j];
        }
    }
    cout << f[n][m] << endl;
}

滚动数组1维优化:

#include<bits/stdc++.h>
using namespace std;

int f[1001],n,v,c[1001],w[1001];

int main()
{
	cin >> n >> v;
	for(int i = 1;i <= n;i++) cin >> c[i] >> w[i];
	for(int i = 1;i <= n;i++)
		for(int vv = v;vv >= c[i];vv--)
			f[vv] = max(f[vv],f[vv - c[i]] + w[i]);
//这里要正序,你可以理解为:f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);
//这里是i的f,而不是i-1的,所以要与01背包不同(i-1)则需要逆序

	cout << f[v];
	return 0;
}

注意:多重背包的遍历顺序

如果求组合数就是外层for循环遍历物品,内层for遍历背包

for  i:物品

      for j:背包大小

如果求排列数就是外层for遍历背包,内层for循环遍历物品

for  j:背包大小

      for i:物品

为什么:第一个理解成固定一个物品,再遍历背包看是否能满足,第二个应该是固定一个背包空间,遍历物品看是否能满足。

例如背包大小为3,·物品有1,2

如果按照第一种方式:先看1,再加入2,只会有{1,2}这种

如果按照第一种方式:背包大小为0,背包大小为1,背包大小为2,只就有{1,2},{2,1}这两种


所以:第一种求的是组合数,第二种求的是排列数。

518,377就是上述两种不一样的方法。

Leetcde 518. 零钱兑换 II

Leetcde 518. 零钱兑换 II

class Solution {
public:

    int change(int amount, vector<int>& coins) {
        int n=coins.size();
        vector<int> dp(amount+1,0);
        dp[0]=1;
        for(int i=0;i<n;i++){
            for(int j=coins[i];j<=amount;j++){
                dp[j]+=dp[j-coins[i]];
            }
        }
        return dp[amount];
    }
};

 Leetcode 377.组合总数Ⅳ

Leetcode 377.组合总数Ⅳ

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
            int n=nums.size();
            vector<int> dp(target+1,0);
            dp[0]=1;
             for(int j=0;j<=target;j++){
                for(int i=0;i<n;i++){
                    if(j>=nums[i]&& dp[j] < INT_MAX - dp[j - nums[i]])
                    dp[j]+=dp[j-nums[i]];
                }
            }
            return dp[target];
    }
};

leetcode 322.零钱兑换

322.零钱兑换

主要在于递推公式,但是不同的是初始化要一个特别大的数字,然后dp[0]=0;

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        const int INT_INF=1e9;
        vector<int> dp(amount+1,INT_INF);
        int n=coins.size();
        dp[0]=0;
        if(amount==0) return 0;
        for(int i=0;i<n;i++){
            for(int j=coins[i];j<=amount;j++){
                dp[j]=min(dp[j],dp[j-coins[i]]+1);
            }
        }
        if(dp[amount]==INT_INF) return -1;

        return dp[amount];
        }
};

   leetcode279.完全平方数

 279.完全平方数

我们抽象以下:

x=a_1^2+a_2^2+a_3^2+...+a_n^2

那么立马就变成完全背包问题:

 物品就是:从1开始一直到100(数据范围),重量就是i^2,价值就是i,然后dp[j]表示,表示第j个数需要的最少的数据个数。

和上面那个题一模一样。

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n+1,INT_MAX);
        dp[0]=0;
        for(int i=1;i<=sqrt(n);i++){
            for(int j=pow(i,2);j<=n;j++){
                dp[j]=min(dp[j],dp[j-pow(i,2)]+1);
            }

        }
        return dp[n];
    }
};

 leetcode129.单词拆分

129.单词拆分

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> wordSet(wordDict.begin(), wordDict.end());//为了find到截取的字符串
        vector<bool> v(s.size()+1,0);
        v[0]=1;
        for(int i=1;i<=s.size();i++){  //字符从0~i开始遍历,指针为i,背包大小
            for(int j=0;j<i;j++){   //i-j用来截取,只有当v[j]==1并且存在截取的子串时才可以
            string sub=s.substr(j,i-j);
                if(v[j]&&wordSet.find(sub)!=wordSet.end()){
                    v[i]=1;
                }
            }
        }
        return v[s.size()];
    }
};

多重背包:

多重背包看做01背包

这个思路就是把多重背包看成是01背包:
 

#include<bits/stdc++.h>
using namespace std;

int n,vv,s[101],v[101],w[101],dp[101];

int main()
{
    cin >> n >> vv;
    for(int i = 1;i <= n;i++) cin >> v[i] >> w[i] >> s[i];
    for(int i = 1;i <= n;i++)//遍历是第几个物品
    {
        for(int j = 0;j < s[i];j++)//遍历第i个物品用了j遍
        {
            for(int k = vv;k >= v[i];k--)
//逆序,遍历从最大背包体积开始,背包大小为k是,向背包放物品。
            {
                dp[k] = max(dp[k],dp[k - v[i]] + w[i]);//递推公式,体积为k时,不选这个物品,就是继承之前的i-1的dp[k],选这个物品,就是背包大小减去v[i],加上w[i]的价值。

            }
        }
    }
    cout << dp[vv];
    return 0;
}

多重背包二进制优化

二进制怎么表示这个10呢  10 = 1 + 2 + 4 + 3,再比如7 就可以用 1 + 2 + 4来表示,只需要枚举3次。这就是我们二进制优化的思想。

比如:第一件物品有v[i]=2(体积),w[i]=3(价值),s[i]=12(数量);可以拆分为:4件如下图所示的物品:(其实本质还是01背包,只不过这时候我们合并了一下,让其装的更快了)

#include<bits/stdc++.h>
using namespace std;

int dp[2001],n,V,v,w,s;

int main()
{
    cin >> n >> V;
    for(int i = 0;i < n;i++)
    {
        cin >> v >> w >> s;//第i个物品,体积,价值,个数
        for(int k = 1;k <= s;k <<= 1)// 以 k <<= 1 实际上是将 k 的值乘以 2。
        {
            for(int j = V;j >= k*v;j--)
            {
                dp[j] = max(dp[j],dp[j-k*v]+k*w);//把第i件多拆分成几件,再做01背包            }
            s -= k;
        }
        if(s != 0)
        {
            for(int j = V;j >= s*v;j--)
            {
                dp[j] = max(dp[j],dp[j-s*v]+s*w);
            }
        }
    }
    cout << dp[V];
    return 0;
}

 单调队列优化

 本题题解来源:AcWing 6. 多重背包问题 III【单调队列优化+图示】 - AcWing

按照完全背包的思路:我们列举如下:

r表示j 

具体图示:

/*

    时间复杂度的分析,我觉得通过代码很难看出来,
    可以通过它的计算过程以及它计算的大体次数来体会。


    比如总体积为V = 10,某个物品对应v=3
    以一个物品为例,我们计算的时候,是把这个物品按照对v取余的结果来分类的。

    v' = 0是一类,这一类有 0, 3, 6, 9      (v'表示当前正在求的体积)
    v' = 1是一类,这一类有 1, 4, 7, 10
    v' = 2是一类,这一类有 2, 5, 8

    我们通过单调队列优化,只是在每一类中进行优化(滑动窗口求最值)
    对于每个物品,我们都会求一遍v' = 0 ~ v' = 10,只是再求的过程中把它们分类了

    一共n个物品,我们会对 物品1 求一遍  v' = 0 ~ v' = 10
                       对 物品2 求一遍  v' = 0 ~ v' = 10
                       ....
                       对 物品n 求一遍  v' = 0 ~ v' = 10

    总共实际求了 n * (v'的最大值), 即 n*m次
    所以时间复杂度是O(n*m)的

*/

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 20020;

int f[N], g[N], q[N];
int n, m;

int main(){

    scanf("%d%d", &n, &m);

    for(int i = 0;i < n;i ++){
        int v, w, s;
        scanf("%d%d%d", &v, &w, &s);
        memcpy(g, f, sizeof f);

        for(int c = 0;c < v;c ++){ // 遍历余数

            int hh = 0, tt = -1;
            for(int j = c;j <= m;j += v){ // 遍历余数为c这一类的 体积

                // 当前层的f[j]  暂时等于 上一层的g[j]  相当于 f[i][j] = f[i-1][j];  也就是s = 0情况
                f[j] = g[j]; 

                // 这里一共有s+1个元素,s=0也算一个,所以这里不是j - s*v + 1
                if(hh <= tt && j - s*v > q[hh]) hh ++; // 队列存的是下标,也是体积 

                // 队列中最大的(s!=0的其中一个)  和 s=0的进行比较
                if(hh <= tt) f[j] = max(f[j], g[q[hh]] + (j-q[hh])/v*w); 

                // q[tt]这个体积下的价值,再加上与j体积相差的体积数的价值,才能与g[j]进行对等比较   
                while(hh <= tt && g[q[tt]] + (j - q[tt])/v*w <= g[j]) tt --; 

                q[++ tt] = j;
            }
        }
    }

    cout << f[m] << endl;

}

状态机

 leetcode089.打家劫舍

089.打家劫舍

用普通线性dp:

从0~i家店铺最大收益:dp[i]=max(dp[i-1],dp[i-2]+nums[i]);

class Solution {
public:
    int rob(vector<int>& nums) {
        int n=nums.size();
        vector<int> dp(n+1);
        
        dp[0]=nums[0];
          if(n==1) {
            return dp[0];
        }
        dp[1]=max(nums[0],nums[1]);
       if(n==2){
            return dp[1];
        }
    
       
        for(int i=2;i<n;i++){
            dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
        }
        return dp[n-1];
    }
};

状态机思路:

引入两个状态:
 

f(i)-------->f(i,0)(未选择最后一个店铺)

|                                  

|

------------>f(i,1)(选择最后一个店铺)

class Solution {
public:
    int rob(vector<int>& nums) {
        vector<vector<int>> dp(nums.size()+1,vector<int>(2,0));
        dp[0][0]=0;
        dp[0][1]=nums[0];
        for(int i=1;i<nums.size();i++){
            dp[i][0]=max(dp[i-1][0],dp[i-1][1]);
            dp[i][1]=dp[i-1][0]+nums[i];
        }
        return max(dp[nums.size()-1][0],dp[nums.size()-1][1]);
    }
};

213.打家劫舍2(补充线性dp)

对打家劫舍1进行分类讨论,分为:选第一个,还是选最后一个,其他都是一模一样。分为0~n-2与1~n-1,两个部分,也就是考虑nums[0],考虑nums[n-1],这两个数的分类讨论。(因为选了0不能选n-1,选了n-1,不能选0)

class Solution {
public:

      int robrange(vector<int>& nums,int l,int r){
        vector<int> dp(nums.size()+1,0);
        dp[l]=nums[l],dp[l+1]=max(nums[l],nums[l+1]);
        for(int i=l+2;i<=r;i++){
            dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[r];
      }
    int rob(vector<int>& nums) {
        int n=nums.size();
        if(n==1) return nums[0];
        if(n==2) return max(nums[0],nums[1]);
        return max(robrange(nums,0,n-2),robrange(nums,1,n-1));
    }
};

  leetcode买股票的最佳时机4

 买股票的最佳时机4

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        vector<vector<vector<int>>> dp(prices.size()+1,vector<vector<int>>(k+1,vector<int>(2,-1e9)));
        //dp[prices.size()+1][k+1][2];
        //考虑0次交易
        for(int i=0;i<=prices.size();i++) dp[i][0][0]=0;
        //dp[i][0][1]=-1e9;表示状态不合法
        //考虑有交易
        //dp[0][1][0]=-1e9;
        dp[0][1][1]=-prices[0];
        for(int i=1;i<prices.size();i++){
            for(int j=1;j<=k;j++){
                dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]+prices[i]);
                dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0]-prices[i]);
            }
        }
        int res=0;
        for(int i=0;i<=k;i++) res=max(res,dp[prices.size()-1][i][0]);
        return res;
    }
};

 在这个基础上,我们直接搞定前面的几道股票题:

leetcode121. 买卖股票的最佳时机

121. 买卖股票的最佳时机

变化:

 dp[i][0]=max(dp[i-1][0] , dp[i-1][1]+prices[i]);
dp[i][1]=max(dp[i-1][1] , -prices[i]);//因为限定交易次数为1,只能交易1次

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        const int inf=-1e9;
        vector<vector<int>> dp(prices.size()+1,vector<int>(2,inf));
        dp[0][0]=0;
        dp[0][1]=-prices[0];
        for(int i=1;i<prices.size();i++){
            dp[i][0]=max(dp[i-1][0] , dp[i-1][1]+prices[i]);
            dp[i][1]=max(dp[i-1][1] , -prices[i]);//因为限定交易次数为1,只能交易1次
        }
     return max(dp[prices.size()-1][0],dp[prices.size()-1][1]);
    }
};

leetcode122. 买卖股票的最佳时机 2

 122. 买卖股票的最佳时机 II

变化:

 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]);//无限交易次数

class Solution {
public:
    int maxProfit(vector<int>& prices) {
    const int inf=-1e9;
        vector<vector<int>> dp(prices.size()+1,vector<int>(2,inf));
        dp[0][0]=0;
        dp[0][1]=-prices[0];
        for(int i=1;i<prices.size();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 max(dp[prices.size()-1][0],dp[prices.size()-1][1]);
    }
};

leetcode123. 买卖股票的最佳时机 3

 123. 买卖股票的最佳时机 III

变化:

 int k=2;即可,其他均不变

class Solution {
        int k=2;
public:
    int maxProfit( vector<int>& prices) {
        vector<vector<vector<int>>> dp(prices.size()+1,vector<vector<int>>(k+1,vector<int>(2,-1e9)));
        //dp[prices.size()+1][k+1][2];
        //考虑0次交易
        for(int i=0;i<=prices.size();i++) dp[i][0][0]=0;
        //dp[i][0][1]=-1e9;表示状态不合法
        //考虑有交易
        //dp[0][1][0]=-1e9;
        dp[0][1][1]=-prices[0];
        for(int i=1;i<prices.size();i++){
            for(int j=1;j<=k;j++){
                dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]+prices[i]);
                dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0]-prices[i]);
            }
        }
        int res=0;
        for(int i=0;i<=k;i++) res=max(res,dp[prices.size()-1][i][0]);
        return res;

    }
};

leetcode买卖股票的最佳时机含冷冻期

 买卖股票的最佳时间含冷冻期

 

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        const int inf=-1e9;
        vector<vector<int>> dp(prices.size(),vector<int>(3,inf));
        dp[0][0]=0;
        dp[0][1]=-prices[0];//第0天买入
        //dp[0][2]=-1e9;
        for(int i=1;i<prices.size();i++){
            dp[i][0]=max(dp[i-1][0],dp[i-1][2]);
            dp[i][1]=max(dp[i-1][0]-prices[i],dp[i-1][1]);
            dp[i][2]=dp[i-1][1]+prices[i];
        }
        return max(dp[prices.size()-1][0],dp[prices.size()-1][2]);
    }
};

leetcode买卖股票的最佳时机含手续费

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

与股票2完全一样,

dp[i][0]=max(dp[i-1][0] , dp[i-1][1]+prices[i]-fee) //卖出股票时完成一次交易,支付手续费
dp[i][1]=max(dp[i-1][1] , dp[i-1][0]-prices[i])

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
    const int inf=-1e9;
        vector<vector<int>> dp(prices.size()+1,vector<int>(2,inf));
        dp[0][0]=0;
        dp[0][1]=-prices[0];
        for(int i=1;i<prices.size();i++){
            dp[i][0]=max(dp[i-1][0] , dp[i-1][1]+prices[i]-fee);
            dp[i][1]=max(dp[i-1][1] , dp[i-1][0]-prices[i]);//无限交易次数
        }
     return max(dp[prices.size()-1][0],dp[prices.size()-1][1]);
    
    }
};

树形dp

leetcode 打家劫舍3

打家劫舍3

dp数组(这里写成PII)的含义:first记录不偷该节点所得到的的最大金钱,second记录偷该节点所得到的的最大金钱。

首先明确的是使用后序遍历。 因为要通过递归函数的返回值来做下一步计算。

通过递归左节点,得到左节点偷与不偷的金钱。

通过递归右节点,得到右节点偷与不偷的金钱。

/**
 * 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) {}
 * };
 */
 typedef pair<int,int> PII;
class Solution {
public:
    int rob(TreeNode* root) {
        PII r=robTree(root);
        int result=max(r.first,r.second);
        return result;
    }
    PII robTree(TreeNode*root){
            if(root==nullptr) return {0,0};
            PII l=robTree(root->left);
            PII r=robTree(root->right);
            int rob_yes=root->val+l.first+r.first;
            int rob_not=max(l.second,l.first)+max(r.first,r.second);// 不偷cur,那么可以偷也可以不偷左右节点,则取较大的情况
            return {rob_not,rob_yes};
    }
};

 leetcode二叉树的直径

二叉树的直径

/**
 * 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 {
  int re=0;

public:
    int diameterOfBinaryTree(TreeNode* root) {
        int r=find(root);
        return re;
    }
   
    int find(TreeNode* root){
        if(root==nullptr) return -1;
        int l=find(root->left)+1;
        int r=find(root->right)+1;
        re=max(re,l+r);
        return max(l,r);
    }
};

 leetcode二叉树中的最大路径和

二叉树中的最大路径和

/**
 * 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 {
    int re=-1e9;
public:
    int maxPathSum(TreeNode* root) {
        int r=maxTreeReturnlrAndSum(root);
        return re;
    }

    int maxTreeReturnlrAndSum(TreeNode*root){
        if(root==nullptr) return 0;
        int l=maxTreeReturnlrAndSum(root->left);
        int r=maxTreeReturnlrAndSum(root->right);
        re=max(re,l+r+root->val);
        return max(max(l+root->val,r+root->val),0);//由于返回值会有负数,所以我们还需要和0,取一个最大值,因为如果是负数,我们可以选择不要
    }
};

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值