算法训练营第四十二天|动态规划:01背包理论基础 416. 分割等和子集

目录

  • 动态规划:01背包理论基础
  • 416. 分割等和子集

动态规划:01背包理论基础

文章链接:代码随想录
题目链接:卡码网:46. 携带研究材料

01背包问题
二维数组解法:

#include 
using namespace std;
 
void slove(int M, int N){
    vector<vector<int>> dp(M, vector<int> (N + 1));
    vector<int> weight(M), value(M);
    for (int i = 0; i < M; i++){
        cin >> weight[i];
    }
    for (int i = 0; i < M; i++){
        cin >> value[i];
    }
    for (int j = 0; j <= N; j++){
        if (j >= weight[0]) dp[0][j] = value[0];
    }
    for (int i = 1; i < M; i++){
        for (int j = 0; j <= N;  j++){
            if (j < weight[i]) dp[i][j] = dp[i - 1][j];
            else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
        }
    }
    cout << dp[M - 1][N] << endl;
}
 
int main(){
    int M, N;
    cin >> M >> N;
    slove(M, N);
    return 0;
}

思路:就是按代码随想录上的那张二维表来看,更新 j 重量下的背包能放0 - i 中多少最大价值的物品;然后一行一行的更新,更新到新物品时,要么就是在 j 重量下放不下,也就是

if (j < weight[i]) dp[i][j] = dp[i - 1][j];

要么能放下就取 原来 或者 新更新物品后背包中的最大值,也就是

else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

其中,

dp[i - 1][j]

代表不放入 i 物品

dp[i - 1][j - weight[i]] + value[i]

代表在 j 重量下先空出weight[i]这么大的空间,然后再放如 i 物品,它可能是本来就有这么大空间,也可能是把其它一些物品拿出去后再放入的 i 物品。

一维(滚动数组)数组解法:

#include 
using namespace std;
 
void slove(int M, int N){
    vector<int> dp(N + 1, 0);
    vector<int> weight(M), value(M);
    for (int i = 0; i < M; i++){
        cin >> weight[i];
    }
    for (int i = 0; i < M; i++){
        cin >> value[i];
    }
     
    for (int i = 0; i < M; i++){
        for (int j = N; j >= weight[i]; j--){
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[N] << endl;
}
 
int main(){
    int M, N;
    cin >> M >> N;
    slove(M, N);
    return 0;
}

一维数组相比二维数组解法就是将每次更新都放在一行上,而且省去了初始化,所以会节省很多空间,这点在后面 leetcode 上的那题会看到比较。另外要注意在遍历重量时是倒序遍历的:

dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

正序遍历会引起重复,而二维数组不会重复是因为每行都用的是上一行的值来更新的。
第一天理解的时候迷迷糊糊,第二天没事时有想了一会突然茅塞顿开了哈哈哈。

416. 分割等和子集

文章链接:代码随想录
题目链接:416. 分割等和子集

思路:01背包应用问题,留足背包的容量,也就是最大总和的一半值加一,如果更新到最后在半值重量的背包中能正好装满,就说明数组可以对半分。
二维数组解法:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for (int i : nums){
            sum += i;
        }

        if (sum % 2 == 1) return false;
        int target = sum / 2;
        vector<vector<int>> dp(nums.size(), vector<int> (10001));

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

        if (dp[nums.size() - 1][target] == target) return true;
        return false;
    }
};

一维(滚动)数组解法:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for (int i : nums){
            sum += i;
        }

        if (sum % 2 == 1) return false;
        int target = sum / 2;
        vector<vector<int>> dp(nums.size(), vector<int> (10001));

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

        if (dp[nums.size() - 1][target] == target) return true;
        return false;
    }
};


这里可以看出两种解法的时间空间对比,显然二维解法有着更大的时间和空间复杂度。因此以后的应用问题尽可能一维(滚动)数组解法。

第四十二天补卡,这两天回学校吃组饭,又耽误了两天,后面那顿饭你不行不去吃了;大体知识能串联起来了,今天开始撸项目背八股,哪不会学哪了,单学效率太低了,争取能在春节后找到个实习,加油!!!

你可能感兴趣的:(算法,动态规划)