题目列表
解题过程
1、1049.最后一块石头的重量II
有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
- 如果
x == y,那么两块石头都会被完全粉碎; - 如果
x != y,那么重量为x的石头将会完全粉碎,而重量为y的石头新重量为y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。
思路: 其实就是将 石头之和/2 ,然后将这个值作为 target ,求解01背包问题,最后 石头之和-2✖️真正装进去的重量 就是所求的 最后一块石头的重量。
动态规划五部曲:
- 确定dp数组以及下标的含义
- dp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背最大重量为dp[j]。
- 确定递推公式
- dp[j] = max(dp[j], dp[j - stones[i]] + stones[i])。
- dp数组初始化
- dp[j]都初始化为0。
- 确定遍历顺序
- 如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!
- 举例推导dp数组
class Solution {
public int lastStoneWeightII(int[] stones) {
int sum = 0;
for (int stone : stones) {
sum += stone;
}
int target = sum >> 1;
int[] dp = new int[target + 1];
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 - 2 * dp[target];
}
}
二维dp数组解法:
class Solution {
public int lastStoneWeightII(int[] stones) {
int sum = 0;
for (int s : stones) {
sum += s;
}
int target = sum / 2;
//初始化,dp[i][j]为可以放0-i物品,背包容量为j的情况下背包中的最大价值
int[][] dp = new int[stones.length][target + 1];
//dp[i][0]默认初始化为0
//dp[0][j]取决于stones[0]
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 - 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];
}
}
}
System.out.println(dp[stones.length - 1][target]);
return (sum - dp[stones.length - 1][target]) - dp[stones.length - 1][target];
}
}
2、494.目标和
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :
- 例如,
nums = [2, 1],可以在2之前添加'+',在1之前添加'-',然后串联起来得到表达式"+2-1"。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
思路: 将整数数组分为两部分,一部分用来计算加法值,另一部分用来计算减法值。加法值部分总和为 x,减法值部分总和为 sum - x,那么 target = x - (sum - x),得 x = (target + sum) / 2,以此为目标值求解01背包问题(方法总数)。
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if (target < 0 && sum < - target) {
return 0;
}
if ((target + sum) % 2 != 0) {
return 0;
}
int newTarget = (target + sum) / 2;
int[] dp = new int[newTarget + 1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
for (int j = newTarget; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[newTarget];
}
}
3、474.一和零
给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。
思路: 本题实质上还是01背包问题,只是这个背包有两个维度,一个是m 一个是n,而不同长度的字符串就是不同大小的待装物品。
动态规划五部曲:
- 确定dp数组以及下标的含义
- dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。
- 确定递推公式
- dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1)。
- dp数组初始化
- 初始化为0。
- 确定遍历顺序
- 一样。
- 举例推导dp数组
class Solution {
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;
// 统计当前字符串中的0和1的个数
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];
}
}