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

news2024/11/14 4:07:42

目录

数字三角形模型

摘花生 

最低通行费

方格取数(洛谷)

传纸条(洛谷) 

最长上升子序列模型

最长上升子序列(洛谷)&最长递增子序列(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,取一个最大值,因为如果是负数,我们可以选择不要
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2071281.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Image-to-Image Translation 图像翻译任务中的输入成对图像拼接成一张图技术详解

引 言 在图像翻译任务中&#xff0c;近几年比较火热的Generative Adversarial Nets (GAN)模型以及各种变体深受视觉研究团体的青睐&#xff0c;在具体任务中取得不错的实验表现。图像翻译包含两部分内容&#xff1a;一个是图像内容(image content)显示内部存在的实体,用于区分不…

【C++ Primer Plus习题】4.5

问题: 解答: #include <iostream> using namespace std;typedef struct _CandyBar {string brand;float weight;int calorie; }CandyBar;int main() {CandyBar snack { "德芙",2.1,20};cout << "品牌:" << snack.brand << endl;…

计算机毕业设计 养老院管理系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

IO进程day01(函数接口fopen、fclose、fgetc、fputc、fgets、fputs)

目录 函数接口 1》打开文件fopen 2》关闭文件fclose 3》文件读写操作 1> 每次读写一个字符&#xff1a;fgetc(),fputc() 针对文件读写 针对终端读写 练习&#xff1a;实现 cat 命令功能 格式&#xff1a;cat 文件名 2> 每次一个字符串的读写 fgets() 和 fputs() …

数据集笔记: FourSquare - NYC and Tokyo Check-ins

FourSquare - NYC and Tokyo Check-ins (kaggle.com) 这个数据集包含了从2012年4月12日到2013年2月16日&#xff0c;约10个月时间内在纽约市和东京收集的签到数据。数据集中包含纽约市的227,428次签到和东京的573,703次签到。每个文件包括以下8个字段&#xff1a; 用户ID&…

Shell脚本学习_运算符

目录 一、算数运算符 1、expr命令&#xff1a; 2、算数运算符介绍&#xff1a; 3、使用 ( ( ) ) 进行运算&#xff1a; 4、bc命令&#xff1a; 1.bc中互动式的数学运算&#xff1a; 2.非互动式的管道运算&#xff1a; 3.非互动式的输入重定向运算&#xff1a; 二、比较运…

Kafka的基本概念

目录 1.Kafka的介绍 1.1介绍 1.2Kafka的概念 1.3.Kafka实现的日志聚合 1.4简单的收发消息 1.5其他消费模式 1.5.1指定消费进度 1.5.2分组消费 1.5.3查看消费者组的偏移量 1.6基于Zookeeper的Kafka集群 1.6.1使用集群的原因 1.6.2Kafka集群架构 1.6.3Topic下的Part…

2024年8月25日 十二生肖 今日运势

小运播报&#xff1a;2024年8月25日&#xff0c;星期日&#xff0c;农历七月廿二 &#xff08;甲辰年壬申月辛酉日&#xff09;&#xff0c;法定节假日。 红榜生肖&#xff1a;龙、牛、蛇 需要注意&#xff1a;鸡、狗、兔 喜神方位&#xff1a;西南方 财神方位&#xff1a;…

UVM中的TLM(事务级建模)通信(2)

上一篇介绍了UVM中利用TLM进行的一对一通信&#xff1a;UVM中的TLM(事务级建模)通信(1)-CSDN博客&#xff0c;除此之外&#xff0c;UVM还有两种特殊的端口&#xff1a;analysis_port和analysis_export&#xff0c;用于完成一对多的通信。 1.analysis端口 这两种端口同样也是用于…

域名泛解析是什么?如何设置?

在当今数字化的时代&#xff0c;网站建设和网络运营对于企业和个人来说都变得至关重要。而在这个过程中&#xff0c;域名的管理和配置起着关键作用。其中&#xff0c;域名泛解析是一个重要的概念&#xff0c;它可以为网站的运营和管理带来诸多便利。 一、域名泛解析是什么&…

尚品汇静态网页设计

目录 尚品汇静态网页设计 在线浏览 项目结果展示 准备 顶部导航条设计 头部设计 主导航区设计 主要内容区设计 左侧边栏区 一级菜单 二级菜单 中间内容区 右侧其他内容区 上部分快报区 下部分图标导航区 秒杀区设计 楼层区设计 顶部设计 详情区设计 页脚设…

ResNet网络学习

简介 Residual Network 简称 ResNet (残差网络) 下面是ResNet的网络结构&#xff1a; ResNet详细介绍 原理 传统方法的问题&#xff1a; 对于一个网络&#xff0c;如果简单地增加深度&#xff0c;就会导致 梯度消失 或 梯度爆炸&#xff0c;我们采取的解决方法是 正则化。…

充电桩系统云快充协议源码(云快充协议1.5 版本源码)

介绍 云快充协议云快充1.5协议云快充协议开源代码云快充底层协议云快充桩直连桩直连协议充电桩系统桩直连协议 软件架构 1、提供云快充底层桩直连协议&#xff0c;版本为云快充1.5&#xff0c;对于没有对接过充电桩系统的开发者尤为合适&#xff1b; 2、包含&#xff1a;启…

搭建智能客服机器人:langgraph实现用户订单管理

大家好&#xff0c;今天我们将创建一个智能客服机器人&#xff0c;它能够记录用户的食物订单到真实数据库中&#xff0c;并允许用户查看他们的订单。这是一个相对高级的Langgraph项目&#xff0c;大家可以先看一下前面介绍的Langgraph的基础课程。 项目概述 我们要构建的系统…

C程序设计——运算符1

条件运算符 这是一个三目运算符&#xff0c;用于条件求值(?:)。 来源&#xff1a;百度百科 这是C语言里&#xff0c;唯一三目&#xff08;即三个表达式&#xff09;运算符。具体格式如下&#xff1a; (表达式1) ? (表达式2) : (表达式3) ; 翻译成人话&#xff0c;就是&…

测试资料4444

一、HTML 1、HTML介绍 1.1web前端三大核心技术 HTML:负责网页架构 CS&#xff1a;负责网页的样式、美化 JS:负责网页的行为 1.2 HTML标签 单标签:<标签名 > 双标签内容 标签属性&#xff1a; 2 常用标签 script:js标签 style:css标签 link:外部加载css标签

SyntaxError: Unexpected token ‘??=‘ 解决办法

问题 原因 Node 15, 及 以上版本才能使用 ?? 操作符 我的版本&#xff1a; 解决 尝试升级node版本 参考 windows下node升级到最新版本&#xff08;亲测有效&#xff09; 有错误&#xff0c;但也创建成功了。 错误以后再改吧…

初识LLM大模型:入门级工程知识探索与解析

前言 源自脉脉上面刷到的大模型业务工程落地可以做的方向。其实如果不是接触相关工作&#xff0c;有的人可能不会想了解这方面&#xff0c;自己实习做的方向与之相关&#xff0c;因此想调研总结一下行业热点方向与基础入门知识&#xff0c;还有一些的专业词汇的解释。包括但不…

异常—python

一、异常 当检测到一个错误时&#xff0c;Python解释器就无法继续执行了&#xff0c;反而出现了一些错误的提示&#xff0c;这就是异常, 也就是我们常说的BUG&#xff0c;那BUG是怎么由来的呢&#xff1f; 例如&#xff1a; print(1/0) 我们在小学的时候就知道0不能作除数&a…

线段树+二分,CF 431E - Chemistry Experiment

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 431E - Chemistry Experiment 二、解题报告 1、思路分析 贪心的考虑&…