7.25 动态规划二刷

This commit is contained in:
zhangsan 2025-07-25 18:23:29 +08:00
parent c323d88ebc
commit b13a106fec
14 changed files with 210 additions and 103 deletions

View File

@ -1,6 +1,5 @@
package dynamic_programming;
import java.util.Arrays;
/**
* 题目 416. 分割等和子集 (CanPartition)
@ -13,64 +12,33 @@ import java.util.Arrays;
* 链接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;
int total=0,target=0;
for (int i = 0; i < nums.length; i++) {
total+=nums[i];
}
// 如果总和为奇数则不可能平分
if (sum % 2 != 0) {
return false;
if(total%2==1)return false;
target=total/2;
int[] dp=new int[target+1];
for (int i = 0; i < nums.length; i++) {
for (int j = target; j >= nums[i]; j--) {
dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i]);
}
}
// 目标值为总和的一半
int target = sum / 2;
// 对数组进行排序为了后续剪枝和高效回溯建议降序排列
Arrays.sort(nums);
reverse(nums);
// 从第0个位置开始回溯寻找是否有子集和为target
return backtrack(nums, target, 0);
return dp[target]==target;
}
public boolean canPartition(int[] nums) {
if(nums == null || nums.length == 0) return false;
int n = nums.length;

View File

@ -1,4 +1,7 @@
package dynamic_programming;
import java.util.Arrays;
/**
* 题目 518. 零钱兑换 II (change)
* 描述给你一个整数数组 coins 表示不同面额的硬币另给一个整数 amount 表示总金额
@ -17,7 +20,31 @@ package dynamic_programming;
* 链接https://leetcode.cn/problems/coin-change-ii/
*/
//二刷会做
public class Change {
public int change1(int amount, int[] coins) {
int[] dp=new int[amount+1];
dp[0]=1;
for (int i = 0; i < coins.length; i++) {
for (int j = coins[i]; j <= amount; j++) {
dp[j]=dp[j]+dp[j-coins[i]];
}
}
return dp[amount];
}
public int change(int amount, int[] coins) {
int[]dp=new int[amount+1];
dp[0]=1;

View File

@ -5,7 +5,7 @@ import java.util.Arrays;
/**
* 题目 322. 零钱兑换 (coinChange )
* 描述给你一个整数数组 coins 表示不同面额的硬币以及一个整数 amount 表示总金额
* 计算并返回可以凑成总金额所需的 最少的硬币个数 如果没有任何一种硬币组合能组成总金额返回 -1
* 计算并返回可以凑成总金额所需的最少的硬币个数如果没有任何一种硬币组合能组成总金额返回 -1
* 你可以认为每种硬币的数量是无限的
示例 2
@ -15,24 +15,39 @@ import java.util.Arrays;
* 链接https://leetcode.cn/problems/coin-change/
*/
//二刷不会
public class CoinChange {
/**
* 可以按照爬楼梯的思路来想amount为目标台阶每一步只能走coins数组中的台阶数dp[i]表示到达第i级台阶所需的最小步数
* 那么自然可以想到dp[i] = min( dp[ i-coins[j] ] )+1其中i - coins[j] >= 0 且j[ 0, len(coins) ]如果i - coins[j] < 0说明走不到目标台阶i会超出台阶i
*/
public int coinChange(int[] coins, int amount) {
int max = amount+1;
int max = amount + 1;
int[] dp = new int[amount + 1];
//初始化dp数组为最大值
Arrays.fill(dp,max);
//当金额为0时需要的硬币数目为0
Arrays.fill(dp, max);
dp[0] = 0;
for (int i = 0; i < coins.length; i++) {
//正序遍历完全背包每个硬币可以选择多次
for (int j = coins[i]; j <= amount; j++) {
//只有dp[j-coins[i]]不是初始最大值时该位才有选择的必要
if (dp[j - coins[i]] != max) {
//选择硬币数目最小的情况
dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] == max ? -1 : dp[amount];
return dp[amount] > amount ? -1 : dp[amount];
}
/**
* 完全背包
*/
public int coinChange2(int[] coins, int amount) {
int []dp=new int[amount+1];
Arrays.fill(dp,amount+1);
dp[0]=0;
for (int i = 0; i < coins.length; i++) {
for (int j = coins[i]; j <= amount; j++) {
dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
}
}
return dp[amount]==amount+1?-1:dp[amount];
}
}

View File

@ -21,7 +21,23 @@ import java.util.Arrays;
* 链接https://leetcode.cn/problems/last-stone-weight-ii/
*/
//二刷会做
public class LastStoneWeightII {
public int lastStoneWeightII1(int[] stones) {
int total=0,target;
for (int stone : stones) {
total+=stone;
}
target=total/2;
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 Math.abs(total-2*dp[target]);
}
public int lastStoneWeightII(int[] stones) {
int sum=0;
for (int stone : stones) {

View File

@ -16,7 +16,24 @@ import java.util.PriorityQueue;
* 链接https://leetcode.cn/problems/longest-increasing-subsequence/
*/
//没做出来
//二刷会做
public class LengthOfLIS {
public int lengthOfLIS1(int[] nums) {
int length=nums.length;
int[] dp=new int[length];
int MMax=1;
Arrays.fill(dp,1);
for (int i = 0; i < length; i++) {
for (int j = 0; j < i; j++) {
if(nums[i]>nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
MMax=Math.max(MMax,dp[i]);
}
}
}
return MMax;
}
public int lengthOfLIS(int[] nums) {
if(nums.length == 0) return 0;
int[] dp = new int[nums.length];

View File

@ -12,7 +12,21 @@ package dynamic_programming;
* 链接https://leetcode.cn/problems/maximum-product-subarray/
*/
//不会
//二刷不会
public class MaxProduct {
/**
* 用动态规划维护遍历到当前位置时 nums[i] 结尾的最大乘积最小乘积
* - prevMax前一步的最大乘积
* - prevMin前一步的最小乘积因为负数相乘会翻转大小
* 对于新元素 x = nums[i]候选值只有三种
* 1. 单独以 x 为一段子数组
* 2. x prevMax 相乘延续最大乘积序列
* 3. x prevMin 相乘延续最小乘积序列有可能翻转成最大
* 因此通过 max(x, x*prevMax, x*prevMin) 得到新的 currMax
* 同理用 min(x, x*prevMax, x*prevMin) 得到 currMin
* 然后更新全局答案 ans = max(ans, currMax)
*/
public int maxProduct(int[] nums) {
if (nums == null || nums.length == 0) {
throw new IllegalArgumentException("数组不能为空");

View File

@ -14,20 +14,34 @@ import java.util.Arrays;
* 链接https://leetcode.cn/problems/perfect-squares/
*/
//二刷会做
public class NumSquares {
public int numSquares(int n) {
int[]dp=new int[n+1];
double sqrted=Math.sqrt(n);
int soft_max=(int) sqrted;
int []dp=new int[n+1];
Arrays.fill(dp,n+1);
dp[0]=0;
for (int i = 1; i <= soft_max; i++) {
for (int j = 1; j <= n; j++) {
int cur= (int) Math.pow(i,2);
if(j>=cur &&dp[j-cur]!=n+1)
dp[j]=Math.min(dp[j],dp[(int) (j-Math.pow(i,2))]+1);
dp[0]= 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j*j<=i; j++) {
dp[i]=Math.min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
// public int numSquares(int n) {
// int[]dp=new int[n+1];
// double sqrted=Math.sqrt(n);
// int soft_max=(int) sqrted;
// Arrays.fill(dp,n+1);
// dp[0]=0;
// for (int i = 1; i <= soft_max; i++) {
// for (int j = 1; j <= n; j++) {
// int cur= (int) Math.pow(i,2);
// if(j>=cur &&dp[j-cur]!=n+1)
// dp[j]=Math.min(dp[j],dp[(int) (j-Math.pow(i,2))]+1);
// }
// }
// return dp[n];
// }
//
}

View File

@ -12,18 +12,8 @@ package dynamic_programming;
* 链接https://leetcode.cn/problems/house-robber/
*/
//二刷会做
public class Rob {
public int rob1(int[] nums) {
int cnt=nums.length;
int[][]dp=new int[cnt][2];
dp[0][0]=0;
dp[0][1]=nums[0];
for (int i = 1; i < cnt; i++) {
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]);
dp[i][1]=dp[i-1][0]+nums[i];
}
return Math.max(dp[cnt-1][0],dp[cnt-1][1]);
}
//推荐这 一维dp
public int rob(int[] nums) {
if (nums == null || nums.length == 0) return 0;

View File

@ -11,27 +11,31 @@ package dynamic_programming;
* 链接https://leetcode.cn/problems/house-robber-ii/
*/
//二刷会做
public class Rob2 {
public int rob(int[] nums) {
int n = nums.length;
if (n == 0) return 0;
if (n == 1) return nums[0];
// 情况一不抢第一家 [1..n-1]
int max1 = robLinear(nums, 1, n - 1);
// 情况二不抢最后一家 [0..n-2]
int max2 = robLinear(nums, 0, n - 2);
return Math.max(max1, max2);
int result1 = robRange(nums, 0, n - 2);
// 情况三不抢第一家 [1..n-1]
int result2 = robRange(nums, 1, n - 1);
return Math.max(result1, result2);
}
// 经典线性打家劫舍区间 [start..end]
private int robLinear(int[] nums, int start, int end) {
int prev2 = 0; // dp[i-2]
int prev1 = 0; // dp[i-1]
for (int i = start; i <= end; i++) {
int curr = Math.max(prev1, prev2 + nums[i]);
prev2 = prev1;
prev1 = curr;
// 198. 打家劫舍 的逻辑区间 [start..end]
private int robRange(int[] nums, int start, int end) {
if (start == end) {
return nums[start];
}
return prev1;
// dp 大小和 nums 一致直接复用索引
int[] dp = new int[nums.length];
dp[start] = nums[start];
dp[start + 1] = Math.max(nums[start], nums[start + 1]);
for (int i = start + 2; i <= end; i++) {
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[end];
}
}

View File

@ -1,11 +1,13 @@
package dynamic_programming;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
/**
* 题目 139. 单词拆分 (wordBreak)
* 描述给你一个字符串 s 和一个字符串列表 wordDict 作为字典如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true
* 注意不要求字典中出现的单词全部都使用并且字典中的单词可以重复使用
* 注意不要求字典中出现的单词全部都使用并且字典中的单词可以 重复 使用
示例 2
输入: s = "leetcode", wordDict = ["leet", "code"]
@ -14,7 +16,21 @@ import java.util.List;
* 链接https://leetcode.cn/problems/word-break/
*/
//二刷不会
public class WordBreak {
public boolean wordBreak1(String s, List<String> wordDict) {
int len=s.length();
boolean[] dp=new boolean[len+1];
HashSet<String>set=new HashSet<>(wordDict);
dp[0]=true;
for (int i = 1; i <= len; i++) {
for (int j = 0; j < i; j++) {
dp[i]=dp[i]||dp[j]&&set.contains(s.substring(j,i));
}
}
return dp[len];
}
public boolean wordBreak(String s, List<String> wordDict) {
int total=s.length();
boolean[] dp=new boolean[total+1];

View File

@ -9,8 +9,8 @@ public class CoinChangeTest {
@Test
public void coinChange() {
CoinChange coinChange = new CoinChange();
int[]coins = {2};
int amount = 1;
int[]coins = {1,2,5};
int amount = 5;
int res=coinChange.coinChange(coins,amount);
System.out.println(res);
}

View File

@ -10,7 +10,7 @@ public class LastStoneWeightIITest {
public void lastStoneWeightII() {
LastStoneWeightII solution = new LastStoneWeightII();
int[]stones = {2,7,4,1,8,1};
int res=solution.lastStoneWeightII(stones);
int res=solution.lastStoneWeightII1(stones);
System.out.println(res);
}
}

View File

@ -0,0 +1,13 @@
package dynamic_programming;
import junit.framework.TestCase;
public class LengthOfLISTest extends TestCase {
public void testLengthOfLIS1() {
int[] a={1,3,6,7,9,4,10,5,6};
LengthOfLIS solution = new LengthOfLIS();
int res=solution.lengthOfLIS1(a);
System.out.println(res);
}
}

View File

@ -0,0 +1,13 @@
package dynamic_programming;
import junit.framework.TestCase;
public class NumSquaresTest extends TestCase {
public void testNumSquares() {
int n=12;
NumSquares numSquares = new NumSquares();
int res=numSquares.numSquares(n);
System.out.println(res);
}
}