当前位置: 首页 > news >正文

算法训练营 day46 动态规划 最后一块石头的重量 II 目标和 一和零

算法训练营 day46 动态规划 最后一块石头的重量 II 目标和 一和零

最后一块石头的重量 II

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

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

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

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。

本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了

是不是感觉和昨天讲解的416. 分割等和子集非常像了。

本题物品的重量为stones[i],物品的价值也为stones[i]。

对应着01背包里的物品重量weight[i]和 物品价值value[i]。

接下来进行动规五步曲:

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

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

  1. 确定递推公式

01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

本题则是:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);

  1. dp数组如何初始化

因为重量都不会是负数,所以dp[j]都初始化为0就可以了,这样在递归公式dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);中dp[j]才不会初始值所覆盖。

  1. 确定遍历顺序

如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!

  1. 举例推导dp数组

举例,输入:[2,4,1,1],此时target = (2 + 4 + 1 + 1)/2 = 4 ,dp数组状态图如下:

在这里插入图片描述

一维dp数组

class Solution {public  int lastStoneWeightII(int[] stones) {int sum = 0;for (int a : stones) {sum += a;}int target = sum / 2;int[] dp = new int[target + 1];for (int i =0; i < stones.length; i++) {for (int j = target; j >=stones[i]; j--) {if (j < stones[i]) {dp[j] = dp[j];} else {dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);}}}return (sum - dp[target]) - dp[target];}
}

二维dp数组

class Solution {public int lastStoneWeightII(int[] stones) {int sum = 0;for (int a : stones){sum += a;}int target = sum/2;int[][] dp = new int[stones.length][target+1];for (int j = stones[0]; j <=target; j++) {dp[0][j] = stones[0];}for (int i = 1; i <stones.length; i++) {for (int j = 1; j <=target; j++) {if (j<stones[i]){dp[i][j] = dp[i-1][j];}else {dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-stones[i]]+stones[i]);}}}return (sum - dp[stones.length - 1][target]) - dp[stones.length - 1][target];}
}

目标和

494. 目标和 - 力扣(LeetCode)

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

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

例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

假设加法的总和为x,那么减法对应的总和就是sum - x。

所以我们要求的是 x - (sum - x) = target

x = (target + sum) / 2

此时问题就转化为,装满容量为x的背包,有几种方法

这里的x,就是bagSize,也就是我们后面要求的背包容量。

大家看到(target + sum) / 2 应该担心计算的过程中向下取整有没有影响。

这么担心就对了,例如sum 是5,S是2的话其实就是无解的

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

    dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法

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

  2. 确定递推公式

    只要搞到nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法。

    凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来。

  3. dp数组如何初始化

    从递推公式可以看出,在初始化的时候dp[0] 一定要初始化为1,因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递推结果将都是0。

  4. 确定遍历顺序

    对于01背包问题一维dp的遍历,nums放在外循环,target在内循环,且内循环倒序。

  5. 举例推导dp数组

    输入:nums: [1, 1, 1, 1, 1], S: 3

    bagSize = (S + sum) / 2 = (3 + 5) / 2 = 4

    dp数组状态变化如下:

    在这里插入图片描述

一维dp数组

class Solution {public  int findTargetSumWays(int[] nums, int target) {int sum = 0;for (int num : nums) {sum += num;}int S = (sum + target) / 2;if (sum < Math.abs(target)) return 0;if ((target + sum) % 2 != 0) return 0;int[] dp = new int[S + 1];dp[0] = 1;for (int i = 0; i < nums.length; i++) {for (int j = S; j >=nums[i]; j--) {dp[j] +=dp[j - nums[i]];}}return dp[S];}
}

二维dp数组

    public static int findTargetSumWays(int[] nums, int target) {int sum = 0;for (int num : nums) {sum += num;}int S = (sum + target) / 2;if (sum < Math.abs(target)) return 0;if ((target + sum) % 2 != 0) return 0;int[][] dp = new int[nums.length + 1][S + 1];dp[0][0] = 1;for (int i = 1; i <= nums.length; i++) {for (int j = 0; j <= S; j++) {if (j < nums[i - 1]) {dp[i][j] = dp[i - 1][j];} else {dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]];}}}return dp[nums.length][S];}

一和零

474. 一和零 - 力扣(LeetCode)

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

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

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

本题中strs 数组里的元素就是物品,每个物品都是一个!

而m 和 n相当于是一个背包,两个维度的背包

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

    dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]

  2. 确定递推公式

    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);

  3. dp数组如何初始化

    因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。

  4. 确定遍历顺序

    物品就是strs里的字符串,背包容量就是题目描述中的m和n。

  5. 举例推导dp数组

    以输入:[“10”,“0001”,“111001”,“1”,“0”],m = 3,n = 3为例

    最后dp数组的状态如下所示:

在这里插入图片描述

    public int findMaxForm(String[] strs, int m, int n) {int[][] dp = new int[m + 1][n + 1];for (String str : strs) {int oneNum = 0, zeroNum = 0;for (char c : str.toCharArray()) {if (c == '0') zeroNum++;else oneNum++;}for (int i = m; i >= zeroNum; i--) {for (int j = n; j >= oneNum; j--) {dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);}}}return dp[m][n];}
http://www.lryc.cn/news/6570.html

相关文章:

  • nginx-host绕过实例复现
  • Java学习记录day9
  • ActiveReports.NET 17.0 Crack by Xacker
  • 【计算机网络】传输层TCP协议
  • Mysql5.7安装【Windows版】
  • 分布式一致性算法Raft原理图释
  • 网络安全-字典生成-crunch
  • 闪光桐人の实习日记
  • PostgreSQL 常见配置参数
  • JAVA 常用类型之String结构
  • 二三层网络设备封装与解封装原理
  • 9、MyBatis框架——使用注解开发实现数据库增删改查操作、一级缓存、二级缓存、MyBatis实现分页
  • C++STL剖析(六)—— set和multiset的概念和使用
  • SpringColud第四讲 Nacos的Windows安装方式和Linux的安装方式
  • 微服务项目【网关服务限流熔断降级分布式事务】
  • 【情人节用Compose给女神写个爱心动画APP】
  • GUI swing和awt
  • 速通Spring
  • 【C++】C++入门
  • Linux网络技术学习(五)—— 网络设备初始化(I)
  • [技术选型] ClickHouse和StarRocks的介绍
  • 算法刷题打卡第90天:表现良好的最长时间段
  • Python语言零基础入门教程(十七)
  • C语言中大小端问题
  • vue2+微前端qiankun从搭建到部署的实践(主子应用切换;集成vue3+vite3子应用)
  • 怎么代理微信小程序创业?
  • 今天是情人节呐,我利用Python制作了好多表白的东西,快来吧~
  • 【Linux】-- 进程信号(处理、内核)
  • C/【静态通讯录】
  • 万卷书 - 让孩子对自己负责 [The Self-Driven Child]