首页 > 编程语言 >算法日记 31 day 动态规划(01背包)

算法日记 31 day 动态规划(01背包)

时间:2024-11-20 15:14:38浏览次数:3  
标签:stones 01 nums int 31 public 数组 day dp

继续来看动态规划中01背包的题目。

题目:最后一块石头的重量II

1049. 最后一块石头的重量 II - 力扣(LeetCode)

有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;
  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0

 题目分析:

        题目的意思就是一堆的石头相互碰撞,碰撞到最后剩下的最小重量。石头两两碰撞,需要保证剩下的部分尽可能的小,是不是就是两块石头的重量尽可能的相当,那么剩下的是不是就小了。同样的,放大到整个石堆,如果我们将石堆分成两个部分,只需要两个部分的总重量尽可能相当即可。而这个重量应该就是所有石头总重量的一半。

        那么这一题就变成了尽可能的凑出这个目标重量,用01背包的视角就是,尽可能的把这个背包装满,这一点和上一题很像,上一题是求能否装满,所以两题的步骤其实大差不差。

  1. 确定dp数组以及下标的含义

dp[i][j]表示从0-i的石头中任取,放到容量为j的背包中,得到的做到重量(二维数组)

dp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背最大重量为dp[j](一维数组)

其他的部分不必多说,和之前的没有差别,这里给出二维dp的写法和一维dp的写法,注意区别。

//二维数组
public class Solution {
    public int LastStoneWeightII(int[] stones) {
        int sum=0;
        for(int i=0;i<stones.Length;i++){
            sum+=stones[i];
        }
        int target=sum/2;
        
        int[][] dp=new int[stones.Length][];//二维数组
        for(int i=0;i<stones.Length;i++){
            dp[i]=new int[target+1];
        }
        //初始化
        for(int i=stones[0];i<=target;i++){
            dp[0][i]=stones[0];
        }

        for(int i=1;i<stones.Length;i++){
            for(int j=1;j<=target;j++){
                if (j >= stones[i]) { //背包容量大于石头重量
                    //不放:dp[i - 1][j] 放:dp[i - 1][j - stones[i]] + stones[i]
                    dp[i][j] = Math.Max(dp[i - 1][j], dp[i - 1][j - stones[i]] + stones[i]);
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return (sum - dp[stones.Length - 1][target]) - dp[stones.Length - 1][target];
    }
}



//一维数组
public class Solution {
    public int LastStoneWeightII(int[] stones) {
        int sum=0;
        for(int i=0;i<stones.Length;i++){
            sum+=stones[i];
        }
        int target=sum/2;
        int[] dp=new int[1501];
        for(int i=0;i<stones.Length;i++){
            for(int j=target;j>=stones[i];j--){
                dp[j]=Math.Max(dp[j],dp[j-stones[i]]+stones[i]);
            }
        }
        return sum - dp[target] - dp[target];
    }
}

题目:目标和

494. 目标和 - 力扣(LeetCode)

给你一个非负整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :

  • 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:

输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
 题目分析:

        因为只有+-两种符号,所以最后得到的式子一定是X-Y=target并且X+Y=sum,可以得出X=(sum+target)/2的结果,对于01背包而已,和之前的差不多,意味着我们需要凑出X这个结果。但是题目要求的是总共有多少中方式,而之前的基本都是求最大。所以这一题的递推公式有所不同。

  1. 确定dp数组以及下标的含义

先用 二维 dp数组求解本题,dp[i][j]:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。

        2.确定递推公式

手动模拟

        对于dp[2,2]而言有3中方式,分别是01,12,02。

那么如果不放2 呢?

        dp[1,2],只有1中方式,是01

如果放2呢?

        需要先将2所需的空间让出在去求,得到的是dp[1][1],2中方式,分别是只放1和只放0。

以上过程,抽象化如下:

  • 不放物品i:即背包容量为j,里面不放物品i,装满有dp[i - 1][j]中方法。

  • 放物品i: 即:先空出物品i的容量,背包容量为(j - 物品i容量),放满背包有 dp[i - 1][j - 物品i容量] 种方法。

本题中,物品i的容量是nums[i],价值也是nums[i]。

递推公式:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];

初始化 

        初始化部分需要考虑物品的价值为0的情况,

如果有两个物品,物品0为0, 物品1为0,装满背包容量为0的方法有几种。

  • 放0件物品
  • 放物品0
  • 放物品1
  • 放物品0 和 物品1

此时是有4种方法。其实就是算数组里有t个0,然后按照组合数量求,即 2^t 。

来看看代码,至于一维数组的解析,这里就不做了,大差不多的优化之和就行。

//二维数组
public class Solution {
    public int FindTargetSumWays(int[] nums, int target) {
        int sum=0;
        for(int i=0;i<nums.Length;i++){
            sum+=nums[i];
        }
        if ((target + sum) % 2 == 1) return 0; // 此时没有方案
        if (Math.Abs(target) > sum) return 0; // 此时没有方案
        int t=(target+sum)/2;

        int[][] dp=new int[nums.Length][];
        for(int i=0;i<nums.Length;i++){
            dp[i]=new int[t+1];
        }
        if (nums[0] <= t) dp[0][nums[0]] = 1; 

        int numZeros=0; 
        for(int i=0;i<nums.Length;i++){
            if(nums[i]==0) numZeros++;
            dp[i][0]=(int)Math.Pow(2,numZeros);
        }

        for(int i=1;i<nums.Length;i++){
            for(int j=1;j<=t;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.Length - 1][t];
    }
}




//一维数组
public class Solution {
    public int FindTargetSumWays(int[] nums, int target) {
        int sum=0;
        for(int i=0;i<nums.Length;i++){
            sum+=nums[i];
        }
        if ((target + sum) % 2 == 1) return 0; // 此时没有方案
        if (Math.Abs(target) > sum) return 0; // 此时没有方案
        int t=(target+sum)/2;

        int[] dp=new int[t+1];
        dp[0]=1;

        //遍历
        for(int i=0;i<nums.Length;i++){
            for(int j=t;j>=nums[i];j--){
                dp[j] =dp[j]+dp[j - nums[i]];
            }
        }
       return dp[t];
    }
}

题目:一和零

474. 一和零 - 力扣(LeetCode)

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

题目分析:

        其实最暴力的就是分别统计每个字符串的01个数,然后去找出符合的子集,显然会超时。

那么用动态规划来解决,注意这里的每个字符只能用一次,只是01背包的问题,而非其他。至于这里的m和n其实是背包的两个维度,不好理解的话,这样,假设有一个水桶他的容量取决于高度和地面的长度,然后我们往里面放东西。

 来看看dp数组

dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。注意这个方式实际上是一维数组的解法,只是因为背包有两个维度,这里写成了二维数组,如果是二维数组的写法,其实是三维数组。

dp的递推公式

dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。

dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。

然后我们在遍历的过程中,取dp[i][j]的最大值。

所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);

public class Solution {
    public int FindMaxForm(string[] strs, int m, int n) {
        int[,] dp = new int[m + 1, n + 1];
        foreach(string str in strs){
            int zero = 0, one = 0;
            for(int i=0;i<str.Length;i++){
                if(str[i]=='0')zero++;
                else one++;
            }

            for (int i = m; i >= zero; i--)
            {
                for (int j = n; j >= one; j--)
                {
                    dp[i, j] = Math.Max(dp[i, j], dp[i - zero, j - one] + 1);
                }
            }
        }
        return dp[m,n];
    }
}

那么对于二维数组的写法,实际上就上加上了关于字符数组的维度。代码部分其实差不太多

public class Solution {
    public int FindMaxForm(string[] strs, int m, int n) {
        int length = strs.Length;
        int[,,] dp = new int[length + 1, m + 1, n + 1];
        for (int i = 1; i <= length; i++) {
            int[] zerosOnes = GetZerosOnes(strs[i - 1]);
            int zeros = zerosOnes[0], ones = zerosOnes[1];
            for (int j = 0; j <= m; j++) {
                for (int k = 0; k <= n; k++) {
                    dp[i, j, k] = dp[i - 1, j, k];
                    if (j >= zeros && k >= ones) {
                        dp[i, j, k] = Math.Max(dp[i, j, k], dp[i - 1, j - zeros, k - ones] + 1);
                    }
                }
            }
        }
        return dp[length, m, n];
    }

    public int[] GetZerosOnes(string str) {
        int[] zerosOnes = new int[2];
        int length = str.Length;
        for (int i = 0; i < length; i++) {
            zerosOnes[str[i] - '0']++;
        }
        return zerosOnes;
    }
}

对于更详细的解析与其他语言的代码块,可以去代码随想录上查看。

代码随想录 (programmercarl.com)

已刷题目:97

标签:stones,01,nums,int,31,public,数组,day,dp
From: https://blog.csdn.net/weixin_70808146/article/details/143909461

相关文章

  • javaweb学习 day4 JavaScript
    JavaScript主要负责网页的行为(交互交过)js引入方式内部脚本:将JS代码定义在HTML页面中1.JS代码必须位于标签之中2.在HTML文档中,常见事件://onload:页面/元素加载完成后触发functionload(){console.log("页面加载完成...")}//onclick:鼠标点击事件functionfn1(){......
  • PbRL | Christiano 2017 年的开山之作,以及 Preference PPO / PrefPPO
    PrefPPO首次(?)出现在PEBBLE,作为pebble的一个baseline,是用PPO复现Christianoetal.(2017)的PbRL算法。Forevaluation,wecomparetoChristianoetal.(2017),whichisthecurrentstate-of-the-artapproachusingthesametypeoffeedback.Theprimarydif......
  • linux学习day03_linux文件与目录管理
    1、相对路径和绝对路径的区别绝对路径:路径的写法“一定由根目录/写起”,例如:/usr/share/doc这个目录。相对路径:路径的写法“不是由/写起”,例如由/usr/share/doc要到/usr/share/man下面时,可以写成:“cd../man”这就是相对路径的写法啦!相对路径意指“相对于目前工作目......
  • springboot农产品小程序-计算机毕业设计源码31670
    摘要 近年来,电子商务的快速发展引起了行业和学术界的高度关注。农产品小程序旨在为用户提供一个简单、高效、便捷的新鲜农产品购物体验,它不仅要求用户清晰地查看所需信息,而且还要求界面设计精美,使得功能与页面完美融合,从而提升系统的可操作性。因此,我们需要深入研究信息内......
  • springboot高校心理咨询管理系统-计算机毕业设计源码31814
    摘 要本论文主要探讨了基于SpringBoot的高校心理咨询管理系统的设计与实现。随着高校心理健康教育的重要性日益凸显,一个高效、便捷的心理咨询管理系统对于提升高校心理咨询服务的质量和效率至关重要。本文首先分析了高校心理咨询管理的现状和需求,然后详细阐述了系统的整......
  • AO3401-ASEMI中低压P沟道MOS管AO3401
    编辑:llAO3401-ASEMI中低压P沟道MOS管AO3401型号:AO3401品牌:ASEMI封装:SOT-23最大漏源电流:-4.2A漏源击穿电压:-30V批号:最新RDS(ON)Max:60mΩ引脚数量:3沟道类型:P沟道MOS管芯片尺寸:MIL漏电流:恢复时间:5ns芯片材质:封装尺寸:如图特性:中低压MOS管、P沟道MOS管工作结温:-55℃~150......
  • 面试精选01-谈谈你对Abp中模块的理解
    模块可以理解成系统中一个独立的功能。例如缓存Redis、队列RabbitMQ、IOC框架Autofac。使用ABP模块可以解决模块之间的依赖问题,通过模块化设计,每个模块可以独立开发、测试和部署,从而减少代码的耦合度,提高了代码的可维护性和复用性,同时使得应用程序更加容易扩展和升级。在A......
  • 国标GB28181-2016平台LiteGBS国标GB28181公网直播的视频监控录像一般需要存储多长时间
    在视频监控领域,有许多软件可供选择,但LiteGBS国标GB28181软件以其独特的优势脱颖而出。在功能方面,与一些传统的视频监控软件相比,LiteGBS国标GB28181软件支持的功能更加丰富多样。它不仅具备基本的视频监控直播、录像检索与回看功能,还拥有云台控制、语音对讲、告警上报、平台级联......
  • Day34--方法的重写
    Day34--方法的重写override重写重写是方法的重写,和属性无关示例:创建下面三个java文件,并在A.javaB.java里面创建方法,Application里面初始化A并引用test方法​ A类是B类的子类packagecom.liu.oop.demo05;publicclassAextendsB{publicstaticvoidtest......
  • 富满 FM5013F SOT23-6 集成充电与电机驱动的控制芯片
    ......