代码随想录算法训练营day36

代码随想录算法训练营

---day36

文章目录


前言

今天是算法营的第36天,希望自己能够坚持下来!

今日任务:

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

● 494. 目标和

● 474.一和零


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

题目链接
文章讲解
视频讲解

动态规划

思路:

其实是尽量让石头分成重量相同的两堆(尽可能相同),相撞之后剩下的石头就是最小的。

  1. 分割等和子集 是求背包是否正好装满,而本题是 求背包最多能装多少。

  2. dp[i][j]的定义为:容量为j的背包,最多可以背最大重量为dp[j]

  3. 递归公式:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);

  4. 初始化:j表示容量,那么最大容量(重量)是多少呢,就是所有石头的重量和。因为提示中给出1 <= stones.length <= 30,1 <= stones[i] <= 1000,所以最大重量就是30 * 1000 。而我们要求的target是最大重量的一半,所以dp数组开到15000大小就可以了。也可以把石头遍历一遍,计算出石头总重量 然后除2,得到dp数组的大小。

  5. 遍历顺序:一维dp数组需要先物品,再遍历背包,且背包是倒序遍历的。

代码如下:

cpp 复制代码
class Solution {
public:
    //问题转化成用背包装接近总和一半的石头,与另一半相撞
    //dp[j]就是容量为j时,背包最多能装的石头重量
    //最后dp[target]里是容量为target的背包所能背的最大重量
    int lastStoneWeightII(vector<int>& stones) {
        int sum = 0;
        for (int num:stones) {
            sum += num;
        }

        int target = sum/2;
        vector<int> dp(15001, 0); //题目说stones长度最大是30,stones[i]最大是100,取一半15000

        for (int i = 0; i< stones.size(); i++) { //遍历物品
            for (int j = target; j >= stones[i]; j--) { //遍历背包
                dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
            }
        }

        return sum - dp[target] - dp[target];
    }
};

二、494.目标和

题目链接
文章讲解
视频讲解

一维dp数组

思路:

把问题转化成,需要将集合分成两组,一组正数,一组负数,两组相加得到目标和,作为正数集合和为left,作为负数集合和为right,得出left - right = target

而且left + right = sum, 可以得出 left = (sum+target)/2

  1. dp[j]:容量为j的背包,有dp[j]种方法可以装到left重量
  2. 递归公式:对于每一个num[i]都可以放,或者不放,
    ①如果放,那么dp[j]就相当于固定了num[i],方法的种数就决定于剩余容量(当前容量j减去num[i]占的容量)装满可以的种数,也就是固定了物品i后, dp[j] = dp[j-num[i]]
    ②那么总的dp[j]就等于所有物品i的dp[j]累加dp[j] += dp[j - nums[i]]
  3. 初始化:如果数组中只有一个[0],target也是0,那么有1种方法 dp[0] = 1;
  4. 遍历顺序:一维dp数组需要先物品,再遍历背包,且背包是倒序遍历的。

代码如下:

cpp 复制代码
class Solution {
public:
    //本题可以理解成需要将集合分成两组,一组正数,一组负数,两组相加得到目标和
    //left + right = sum
    //left - right = target , right = left - target
    //left + left - terget = sum -> left = (sum+target)/2 得出正数和可由sum和target得到
    //再转化成在集合nums中找出和为left的组合
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        for (int num:nums) {
            sum += num;
        }

        //当tartget大于总和无解,每个数只能用一次
        if (abs(target) > sum) return 0;
        //当求出来的left不为整数时说明无解
        if ((sum + target)%2 == 1) return 0;
        int left = (sum + target)/2;
        
        //dp[j]含义:容量为j的背包,有dp[j]种方法可以装到left重量
        //初始化:如果数组中只有一个[0],target也是0,那么有1种方法
        //递推公式:对于每一个num[i]都可以放,或者不放,
        //如果放,那么dp[j]就相当于固定了num[i],方法的种数就决定于剩余容量(当前容量j减去num[i]占的容量)装满可以的种数,也就是固定了物品i后, dp[j] = dp[j-num[i]]
        //总的dp[j]就等于所有物品i的dp[j]累加dp[j] += dp[j - nums[i]]
        vector<int> dp(left + 1, 0);
        dp[0] = 1;

        for (int i = 0; i < nums.size(); i++) {
            for (int j = left; j >= nums[i]; j--) { //对于每个容量j,累加所有能放下的物品i的dp[j]
                dp[j] += dp[j - nums[i]]; 
            }
        }

        return dp[left];
    }
};

二维dp数组

代码如下:

cpp 复制代码
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        for (int i = 0; i < nums.size(); i++) sum += nums[i];
        if (abs(target) > sum) return 0; // 此时没有方案
        if ((target + sum) % 2 == 1) return 0; // 此时没有方案
        int bagSize = (target + sum) / 2;
        
        vector<vector<int>> dp(nums.size(), vector<int>(bagSize + 1, 0));
        
        // 初始化最上行
        if (nums[0] <= bagSize) dp[0][nums[0]] = 1; 

        // 初始化最左列,最左列其他数值在递推公式中就完成了赋值
        dp[0][0] = 1; 

        int numZero = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] == 0) numZero++;
            dp[i][0] = (int) pow(2.0, numZero);
        }

        // 以下遍历顺序行列可以颠倒
        for (int i = 1; i < nums.size(); i++) { // 行,遍历物品
            for (int j = 0; j <= bagSize; j++) { // 列,遍历背包
                if (nums[i] > j) dp[i][j] = dp[i - 1][j]; 
                else dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];
            }
        }
        return dp[nums.size() - 1][bagSize];
    }
};

三、474. 一和零

题目链接
文章讲解
视频讲解

思路:

strs 数组里的元素就是物品,每个物品都是一个,仍然是01背包问题,只是背包有两个。

  1. dp[i][j]:i个0和j个1的最大子集长度
  2. 递推公式: 对于当前字符串,可以选择加入子集或者不加入,
    ①加入就是先空出当前字符串所包含的0和1的个数,再+1,dp[i-zeroNum][j-oneNum]+1
    ②然后遍历的时候就取每个字符串所求出来的dp[i][j]的最大值
    所以得出,dp[i][j] = max(dp[i][j], dp[i-zeroNum][j-oneNum] + 1)
  3. 初始化:01背包的滚动数组的dp数组初始化为0就可以
  4. 遍历顺序:先遍历物品,再遍历背包(本题是有两个背包,m和n)
  5. 举例推导dp数组:
    以输入:["10","0001","111001","1","0"],m = 3,n = 3为例,如下:

代码如下:

cpp 复制代码
class Solution {
public:
    //dp[i][j]:i个0和j个1的最大子集长度
    //递推公式: 对于当前字符串,可以也是选择加入子集或者不加入,
    //加入就是先空出当前字符串所包含的0和1的个数,再+1,dp[i-zeroNum][j-oneNum]+1
    //然后遍历的时候就取每个字符串所求出来的dp[i][j]的最大值
    //dp[i][j] = max(dp[i][j], dp[i-zeroNum][j-oneNum] + 1)
    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 zeroNum = 0, oneNum = 0;
            //统计当前字符串的0和1的个数
            for (char c : str) {
                if (c == '0') zeroNum++;
                else oneNum++;
            }

            for (int i = m; i >= zeroNum; i--) { //后序遍历背包容量,遍历0和1两个维度
                for (int j = n; j>= oneNum; j--) {
                    dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
                }
            }
        }

        return dp[m][n];
    }
};

总结

不同类型的背包问题,

  • 分割等和子集:求是否刚好装满背包,
    dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) 求dp[target] == target
  • 最后一块石头的重量 II:尽可能装满背包,
    dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) 求sum - dp[target] - dp[target]
  • 目标和 :求装满背包的方法数,
    dp[j] += dp[j- weight[j]]
  • 一和零:求装满背包用的最多物品数量(有两个背包),
    dp[i][j] = max(dp[i][j], dp[i - 物品重量1][j - 物品重量2] + 1)

明天继续加油!

相关推荐
qy发大财4 分钟前
验证二叉搜索树(力扣98)
数据结构·算法·leetcode·职场和发展
人类群星闪耀时21 分钟前
用深度学习优化供应链管理:让算法成为商业决策的引擎
人工智能·深度学习·算法
比特在路上32 分钟前
蓝桥杯之c++入门(一)【数据类型】
c++·职场和发展·蓝桥杯
hy____12337 分钟前
动态内存管理
linux·运维·算法
菜菜小蒙1 小时前
【C++】特殊类设计、单例模式与类型转换
开发语言·c++
m0_675988231 小时前
Leetcode40: 组合总和 II
算法·leetcode·回溯·排序·python3
Joyner20182 小时前
python-leetcode-分隔链表
算法·leetcode·链表
水瓶丫头站住3 小时前
用C++编写一个2048的小游戏
c++
Wyyyyy_m3 小时前
牛客训练营(一)补题
c++·算法
Ciderw3 小时前
TCP三次握手和四次挥手
开发语言·网络·c++·后端·网络协议·tcp/ip·golang