1049. 最后一块石头的重量 II
链接
文章链接
题目链接
第一想法
这道题的要求是石头两两撞碎,问最后一块石头的最小重量,所以是,可以把石头堆分为重量最相近的两堆,差值即为所求,分析到这一点就知道这道题怎么想了,因为刚做了一道把数组分为两个相等的子数组。五部曲
- dp数组的含义,这里的dp[j]表示背包容量为j时的最大价值(这里的价值和重量都是石头的大小)
- 初始化,很容易想,都是为0的
- 递推公式
dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i])
- 遍历顺序也是一样的,因为这里是滚动数组,所以必须先是物品后是背包
//一维数组
function lastStoneWeightII(stones: number[]): number {
let sum: number = 0
for (let i = 0; i < stones.length; i++) {
sum += stones[i]
}
let dp: number[] = new Array(~~(sum / 2) + 1).fill(0)
for (let i = 0; i < stones.length; i++) {//物品
for (let j = dp.length - 1; j >= stones[i]; j--) {//背包
dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i])
}
}
return Math.abs(sum - 2 * dp[dp.length - 1])
}
看完文章后的想法
文章的讲解更加全面,这里就附一张图来表示手动推导递推公式的过程:
同时我的return返回值可以为
sum - 2 * dp[dp.length - 1]
,因为dp数组咱们是向下取整的,可以不用有取绝对值的过程。
思考
这道题不算太难,如果你能想到把石头堆分为两个质量最相近的石头堆的话,这道题就是416. 分割等和子集这道题一样了。不同只是最后的返回值不一样罢了
494. 目标和
链接
题目链接
文章链接
第一想法
emmm,刚开始就想错了,我刚开始是选几个数的运算后和为target,结果是每个数都要使用,然后就不会了。。。
看完文章后的想法
很妙,这道题对于我来说确实不容易想,因为运算完和为target,所以可以把数组分为两堆,一堆和为left,一堆和为right(这里假定left>=right),所以就满足这个等式left+right=sum
,left-right=target
,所以转换为left=(sum+target)/2
,所以就需要和为left有多少种方法就可以了,由这个等式可以看出,当(sum+target)%2===1时,一定是不可能达成和为left的,因为nums都是正数。同时如果Math.abs(target) > sum
也是不能的,接下来递归五部曲
- dp数组的含义为和为j的方法数为dp[j]
- 初始化,这个初始化不好想,原因就是dp[0]为0还是为1,这个是为1的,原因和递推公式有关,如果为0的话每一个dp[j]都是为0的
- 递推公式为
dp[j] += dp[j - nums[i]]
原因为假设j=5,则dp[5]的意思为和为5的方法数,所以方法数为5-nums[i]对应的方法数和 - 遍历顺序还是为滚动数组的便利顺序是一样的
function findTargetSumWays(nums: number[], target: number): number {
let sum: number = 0
for (let i = 0; i < nums.length; i++) {//求和
sum += nums[i]
}
if (Math.abs(target) > sum || (sum + target) % 2 === 1) return 0//left如果携带小数或者Math.abs(target) > sum 则一定不可能有表达式为target
let dp: number[] = new Array((sum + target) / 2 + 1).fill(0)//dp数组
dp[0] = 1//初始化 很重要!!!!
for (let i = 0; i < nums.length; i++) {
for (let j = (sum + target) / 2; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]]
}
}
return dp[dp.length - 1]
}
思考
这道题比较难,确实没想到,核心思想就是要通过left+right=sum
和left-right=target
两个公式推出left=(sum+target)/2
,因为只有推出left为什么,才能想到用0-1背包解决这个问题
474. 一和零
链接
文章链接
题目链接
第一想法
emmmmm,只要题转变一点思路的我就没啥想法了。。看文章了。。。
看完文字的想法
看完文字后,我的想法只有一个字妙!,对于动态规划还是得继续努力,这道题有点奇特,虽然你表面看dp数组是二维数组,但其实是按照滚动数组的解法来解问题的,接下来递归五部曲
- dp数组的含义dp[i][j]表示0的容量为i个,1的容量为j个时子集的最大长度
- 初始化,很简单 都初始为0就可以了
- 递推公式 还是按照滚动数组的写法
dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1)
不算当前字符串的子集长度与算上当前字符串的子集长度取最大值 - 遍历顺序,这里的便利顺序很重要,对于strs的遍历顺序是无关的,但是对于i和j的遍历顺序一定是倒序的,原因是防止一个字符串被用了多次
function findMaxForm(strs: string[], m: number, n: number): number {
let dp: number[][] = new Array(m + 1).fill(0).map((item) => new Array(n + 1).fill(0))//dp数组i为0的容量 j为1的容量
for (const str of strs) {//物品遍历
let zeroNum: number = 0
let oneNum: number = 0
for (let i = 0; i < str.length; i++) {//这个是计算这个字符串0和1的个数
if (str[i] == '0') zeroNum++
else oneNum++
}
//背包遍历 这个背包的容量必须要满足两个条件 0的容量大于0的个数 1的容量大于1的个数
for (let i = m; i >= zeroNum; i--) {
for (let j = n; j >= oneNum; j--) {
dp[i][j] = Math.max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1)//这里i - zeroNum和j - oneNum是防止0和1超过背包容量
}
}
}
return dp[m][n]
}
思考
这道题对我来说,有点难,同时也有点大意,没想到二维数组也是可以看出滚动数组的,所以这道题感觉写不出来就看答案了,有点携带了,这道题如果认认真真分析的话应该是能想到一部分思路的
今日总结
今天三道题,一共耗费3小时,其中后两道没做出来,对我来说确实有点难了。看来动态规划的思路还是得继续学习。