前言
动态规划专题,从简到难通关动态规划。
每日一题
今天的题目是 1049. 最后一块石头的重量 II,难度为中等
有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
- 如果
x == y,那么两块石头都会被完全粉碎; - 如果
x != y,那么重量为x的石头将会完全粉碎,而重量为y的石头新重量为y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。
示例 1:
输入: stones = [2,7,4,1,8,1] 输出: 1 解释: 组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1], 组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1], 组合 2 和 1,得到 1,所以数组转化为 [1,1,1], 组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
示例 2:
输入: stones = [31,26,33,21,40] 输出: 5
提示:
1 <= stones.length <= 301 <= stones[i] <= 100
题解
动态规划
对于石头来说,每两个石头之间可以取差值,我们最终的目的,其实也就是要剩下差值最小的石头,比如说 [1,1,1] 这三个分为两个堆,差值为1,再比如[1,2,3,4] 这个也可以分为两个堆 分别是 1+4和2+3,差值为0,也就是最后的结果为0,我们就会发现,我们优先把石头分为两堆,两堆之间的差值,就会使相互抵消最后得到的最优解。
那么这道题也转为了动态规划的问题,dp[i][j] 代表着背包大小为 j 的情况下,i怎么选择才能够更加的接近 j ,最优的情况当然是刚好等于,那就能够完全消掉,这里的 j 代表了实际指的是 sum/2,也就是怎么选择物品,才能够使得值最接近 sum/2
那么接下来用动态规划五部曲来进行解题
dp 数组含义:
定义一个二维数组 dp[i][j],表示前 i 个石头装入容量为 j 的背包中所能装下的最大重量。
递推公式:
对于第 i 个石头(即 stones[i-1]),有两种选择:装或者不装。 若当前石头的重量大于容量 j,不装入该石头:dp[i][j]=dp[i-1][j] 若当前石头的重量小于等于容量 j,装或不装该石头:
dp[i][j]=max(dp[i-1][j], dp[i-1][j-stones[i-1]]+stones[i-1])
其中,dp[i-1][j] 表示不装当前石头的最大重量,dp[i-1][j-stones[i-1]]+stones[i-1] 表示装入当前石头的最大重量。
数组初始化:
dp[0][0] = 0,表示装入一个重量为 0 的物品的最大价值为 0。 为了保证数组完整性,需要将 dp 数组的其他元素初始化为 0。
遍历顺序:
外层循环遍历每个石头(i 从 1 到 n),内层循环遍历容量(j 从 0 到 target),对于每个石头和容量,根据递推公式计算 dp[i][j] 的最大重量。
function lastStoneWeightII(stones: number[]): number {
const sum = stones.reduce((acc, cur) => acc + cur, 0);
const target = Math.floor(sum / 2);
const n = stones.length;
const dp = new Array(n + 1).fill(0).map(() => new Array(target + 1).fill(0));
for (let i = 1; i <= n; i++) {
for (let j = 0; j <= target; j++) {
if (j < stones[i-1]) {
dp[i][j] = dp[i-1][j];
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-stones[i-1]] + stones[i-1]);
}
}
}
const sumA = dp[n][target];
const sumB = sum - sumA;
return Math.abs(sumB - sumA);
};