4.12 0/1背包

This commit is contained in:
zhangsan 2025-04-12 12:04:18 +08:00
parent 515479d66e
commit a7d420dc78
4 changed files with 172 additions and 1 deletions

View File

@ -0,0 +1,97 @@
package dynamic_programming;
import java.util.Arrays;
/**
* 题目 416. 分割等和子集 (CanPartition)
* 描述给你一个 只包含正整数 非空 数组 nums 请你判断是否可以将这个数组分割成两个子集使得两个子集的元素和相等
示例 2
输入nums = [1,5,11,5]
输出true
解释数组可以分割成 [1, 5, 5] [11]
* 链接https://leetcode.cn/problems/partition-equal-subset-sum/
*/
public class CanPartition {
private void reverse(int[] nums) {
int left = 0, right = nums.length - 1;
while (left < right) {
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
left++;
right--;
}
}
private boolean backtrack(int[] nums, int target, int start) {
// 若已经完全匹配到目标和返回 true
if (target == 0) {
return true;
}
// 如果目标和已经小于0说明该分支不可行返回 false
if (target < 0) {
return false;
}
// start 开始尝试使用后续的每个数
for (int i = start; i < nums.length; i++) {
// 若当前数字超过 target则可以跳过
if (nums[i] > target) {
continue;
}
// 选择当前数字并尝试凑出剩余的 target
if (backtrack(nums, target - nums[i], i + 1)) {
return true;
}
}
return false;
}
//回溯法超时了
public boolean canPartition1(int[] nums) {
// 计算数组总和
int sum = 0;
for (int num : nums) {
sum += num;
}
// 如果总和为奇数则不可能平分
if (sum % 2 != 0) {
return false;
}
// 目标值为总和的一半
int target = sum / 2;
// 对数组进行排序为了后续剪枝和高效回溯建议降序排列
Arrays.sort(nums);
reverse(nums);
// 从第0个位置开始回溯寻找是否有子集和为target
return backtrack(nums, target, 0);
}
public boolean canPartition(int[] nums) {
if(nums == null || nums.length == 0) return false;
int n = nums.length;
int sum = 0;
for(int num : nums) {
sum += num;
}
//总和为奇数不能平分
if(sum % 2 != 0) return false;
int target = sum / 2;
int[] dp = new int[target + 1];
for(int i = 0; i < n; i++) {
for(int j = target; j >= nums[i]; j--) {
//物品 i 的重量是 nums[i]其价值也是 nums[i]
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
//剪枝一下每一次完成內層的for-loop立即檢查是否dp[target] == target優化時間複雜度26ms -> 20ms
if(dp[target] == target)
return true;
}
return dp[target] == target;
}
}

View File

@ -6,7 +6,6 @@ import java.util.List;
/** /**
* 题目 118. 杨辉三角 (generate) * 题目 118. 杨辉三角 (generate)
* 描述给定一个非负整数 numRows生成杨辉三角的前 numRows * 描述给定一个非负整数 numRows生成杨辉三角的前 numRows
*
* 杨辉三角每个数是它左上方和右上方的数的和 * 杨辉三角每个数是它左上方和右上方的数的和
示例 2 示例 2

View File

@ -0,0 +1,31 @@
package dynamic_programming;
/**
* 题目 343. 整数拆分 (IntegerBreak)
* 描述给定一个正整数 n 将其拆分为 k 正整数 的和 k >= 2 并使这些整数的乘积最大化
* 返回 你可以获得的最大乘积
示例 2
输入: n = 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
* 链接https://leetcode.cn/problems/integer-break/
*/
public class IntegerBreak {
public int integerBreak(int n) {
//dp[i] 为正整数 i 拆分后的结果的最大乘积
int[] dp = new int[n+1];
dp[2] = 1;
for(int i = 3; i <= n; i++) {
for(int j = 1; j <= i-j; j++) {
// 这里的 j 其实最大值为 i-j,再大只不过是重复而已
//并且在本题中我们分析 dp[0], dp[1]都是无意义的
//j 最大到 i-j,就不会用到 dp[0]与dp[1]
dp[i] = Math.max(dp[i], Math.max(j*(i-j), j*dp[i-j]));
// j * (i - j) 是单纯的把整数 i 拆分为两个数 也就是 i,i-j 再相乘
//而j * dp[i - j]是将 i 拆分成两个以及两个以上的个数,再相乘
}
}
return dp[n];
}
}

View File

@ -0,0 +1,44 @@
package dynamic_programming;
/**
* 题目 96. 不同的二叉搜索树 (IntegerBreak)
* 描述给你一个整数 n 求恰由 n 个节点组成且节点值从 1 n 互不相同的 二叉搜索树 有多少种返回满足题意的二叉搜索树的种数
示例 2
输入n = 3
输出5
* 链接https://leetcode.cn/problems/unique-binary-search-trees/
*/
//这道题要先推导然后构建dp状态转移方程 不要被数值所困扰就考虑左右子树有几个节点分别能怎样排列组合
public class NumTrees {
public int numTrees(int n) {
// 定义dp数组其中dp[i]表示由i个节点组成的不同二叉搜索树的个数
int[] dp = new int[n + 1];
// 初始化基础情况空树和只有一个节点的树
dp[0] = 1; // 空树算一种
dp[1] = 1; // 单节点树只有一种
// 自底向上逐步计算dp[i]
// i代表当前要构造树的节点数
for (int i = 2; i <= n; i++) { //外层循环确保在计算 dp[i] 所有比 i 小的 dp 值已经算出
// 枚举所有可能的根节点k从1到i
for (int k = 1; k <= i; k++) {
// 左子树由 k - 1 个节点构成右子树由 i - k 个节点构成 因为二叉搜索树左子树中的节点一定小于根节点
// 这两部分构造方式相互独立因此总方法数相乘
dp[i] += dp[k - 1] * dp[i - k];
}
}
// 返回由n个节点组成的不同二叉搜索树个数
return dp[n];
}
// 可用于简单测试
public static void main(String[] args) {
NumTrees solution = new NumTrees();
int n = 3;
int result = solution.numTrees(n);
System.out.println("当 n = " + n + " 时,不同的二叉搜索树个数为:" + result); // 输出 5
}
}