代码随想录算法训练营第四十三天|1049. 最后一块石头的重量 II、494. 目标和、474.一和零
最后一块石头的重量
1049. 最后一块石头的重量
文章讲解:https://programmercarl.com/1049.%E6%9C%80%E5%90%8E%E4%B8%80%E5%9D%97%E7%9F%B3%E5%A4%B4%E7%9A%84%E9%87%8D%E9%87%8FII.html
题目链接:https://leetcode.cn/problems/last-stone-weight-ii/
视频讲解:https://www.bilibili.com/video/BV14M411C7oV/
自己看到题目的第一想法
动态规划五步骤:
- dp[j]含义:从下标为[0-j]的物品里任意取,放进背包后,价值总和最大是多少。
- 确定递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
- dp数组初始化:dp[0] = 0;飞、非0下标初始化为0。
- 遍历顺序:一维数组,从后往前遍历
看完代码随想录之后的想法
先列出物品的重量、价值表格。
重量 | 价值 | |
---|---|---|
物品0 | 1 | 15 |
物品1 | 3 | 20 |
物品2 | 4 | 30 |
得到dp[i][j]的定义
动态规划五步骤:
-
dp[i][j]的定义:dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
-
递推公式:不放物品i的时候,最大价值为dp[i-1][j](其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。因此在循环时会有背包大小的判断);放物品i的时候最大价值为dp[i-1][j - wight[i]] + value[i](当i放进去时,那么这时候整个物品集就被分成两部分,1到i-1和第i个,而这是i是确定要放进去的,那么就把j空间里的wight[i]给占据了,只剩下j-wight[i]的空间给前面i-1,那么只要这时候前面i-1在j-wi空间里构造出最大价值,即dp[i-1][j - wight[i]],再加上此时放入的i的价值vi,就是dpij了);所以递推公式dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j - wight[i]] + value[i]);见下面表格,dp[i][j]由其上一个节点以及左上某一节点得到。
-
初始化值:画出背包重量和物品的二维数组图。见下图可以得出dp[0][j]这一行的值为15。dp[i][0]这一列的初始值为0。
-
遍历顺序
- for() ----- 物品
- for()----背包
这里进行遍历的时候需要增加一个当前重量放不下的判断。
- for()----背包
- for() ----- 物品
自己实现过程中遇到哪些困难
将2个数组入参改为二维数组,然后处理逻辑。
做动态规划的题目,最好的过程就是自己在纸上举一个例子把对应的dp数组的数值推导一下,然后在动手写代码!
目标和
494. 目标和
文章讲解:https://programmercarl.com/%E8%83%8C%E5%8C%85%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%8001%E8%83%8C%E5%8C%85-2.html
题目链接:https://leetcode.cn/problems/target-sum/
视频讲解:https://www.bilibili.com/video/BV1o8411j73x
自己看到题目的第一想法
想不出来。
看完代码随想录之后的想法
这里看代码随想录没理解,后面看了leetcode官方题解
代码随想录
输入的所有数字可以分为两组,一组是正数组,一组是负数组。
两组的数字求和后为sum。两组的数字求差后为target。
left + right = sum。
left - right = target。
target是固定的,sum是固定的,left就可以求出来。left = (sum + target) / 2。
这里的推导可以转换为,装满容量为left的背包有几种方法。
- 当left = (sum + target) / 2计算过程中出现未除尽情况,此时无解。(因为数组中的元素都为非负整数,因此left也必须是非负整数)
- 同理如果target的绝对值大于sum,也是没有方案的。
leetCode官方题解
使用二维数组,整体和代码随想录前面差不多,记数组的元素和为 sum,添加 - 号的元素之和为 neg,则其余添加 + 的元素之和为 sum−neg。求得neg = (sum-target)/2。
由于数组nums中的元素都是非负整数,neg也必须是非负整数,因此除不尽的情况下直接返回0。
动态规划五步骤:
-
定义二维数组dp,dp[i][j]表示在数组nums的前i个数中选取元素,使得这些元素之和等于j的方案数。假设数组nums的长度为n,则最终答案为dp[n][neg]。
-
确定推导公式
-
确定初始化值,当没有任何元素可选时,元素和只能是0。对应的方案数是1,dp[0][0] = 1;其他情况下dp[0][j] = 0。
自己实现过程中遇到哪些困难
一和零
474.一和零
文章讲解:https://programmercarl.com/0474.%E4%B8%80%E5%92%8C%E9%9B%B6.html
题目链接:https://leetcode.cn/problems/ones-and-zeroes/
视频讲解:https://www.bilibili.com/video/BV1rW4y1x7ZQ
自己看到题目的第一想法
看完代码随想录之后的想法
将题目想成一个容器,最多m个0,n个1。
动态规划五步骤:
- dp数组定义,定义一个二维dp数组(一维数组没法表现m、n以及最多多少个物品)。装满i个0,j个1,最多背多少个物品。最大背dp[i][j]个物品 -> dp[m][n]。dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。
- 确定递推公式:该题每个物品的重量为x个0,y个1。总体是即m个0,n个1.每次放进一个字符时,该字符中的0的个数即为x,1的个数即为y。则当前的物品数量为dp[i-x][j-y]+1(前面dp[i-x][j-y]表示没放x,y时的数量。后面+1表示放了x,y这个字符的,价值(物品数量)为1)
- dp[i][j] = max(dp[i-x][j-y] + 1,dp[i][j])。
- 确定初始化值:dp[0][0]=0,因为背包容量(这里表示m,n为0)为0了,所装的最大个数自然为0;其他非0的初始化成0,不能初始为integer最大值,因为要取max。dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。
- 确定遍历顺序:先遍历物品,再遍历背包。遍历背包时倒序遍历。物品是字符串,背包是X和Y。i从m开始,大于等于x再–;j从n开始,大于等于y再–。(迭代格式中,某个单元的值由前面的单元得来,所以你先更新前面的时候,后面的值本来是应该上一时刻的值,但是顺序计算时,旧的左值被新值覆盖了。)
自己实现过程中遇到哪些困难
基本上算是复制了代码,其实看完也没看懂。这一节课卡的时间太长了,快速过了先。后面二刷
public int findMaxForm(String[] strs, int m, int n) {
//dp[i][j]表示i个0和j个1时的最大子集
int[][] dp = new int[m + 1][n + 1];
int oneNum, zeroNum;
for(String str : strs){ // 遍历背包
oneNum = 0;
zeroNum = 0;
for (char ch : str.toCharArray()) {
if (ch == '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];
}
今日收获&学习时长
这节课卡了很久,后续背包类型题目全部都需要二刷,基本没有想出来的。