From 09b670943bb8a1493994f0845a00498ff401017b Mon Sep 17 00:00:00 2001 From: markilue <745518019@qq.com> Date: Fri, 25 Nov 2022 15:16:42 +0800 Subject: [PATCH] =?UTF-8?q?leecode=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../leecode/dynamic/T08_CanPartition.java | 138 ++++++++++++++++++ .../dynamic/T09_LastStoneWeightII.java | 78 ++++++++++ 2 files changed, 216 insertions(+) create mode 100644 Leecode/src/main/java/com/markilue/leecode/dynamic/T08_CanPartition.java create mode 100644 Leecode/src/main/java/com/markilue/leecode/dynamic/T09_LastStoneWeightII.java diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T08_CanPartition.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T08_CanPartition.java new file mode 100644 index 0000000..935b621 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T08_CanPartition.java @@ -0,0 +1,138 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +import java.lang.reflect.Array; +import java.util.Arrays; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: markilue + * @CreateTime: 2022-11-25 10:06 + * @Description: TODO 力扣416题 分割等和子集: + * 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 + * @Version: 1.0 + */ +public class T08_CanPartition { + + @Test + public void test() { + int[] nums = {1, 5, 11, 5}; + int[] nums1 = {1, 2, 3, 5}; + System.out.println(canPartition1(nums)); + } + + /** + * 自己思路:这题使用背包问题来分析的话,可以发现背包的最大容量可以确定--所有数之和除2,如果是奇数直接false如果是偶数转为背包问题 + * 1)首先:是不是0-1背包问题?虽然数组里的数可以有重复的,但是重复的数据也是彼此独立的,不是说一个数想用几次就用几次,所以是0-1背包问题 + * 2)其次:数组需不需要先进行排序?应该是不需要的,无论是动规的状态转移方程或者是其他的都是max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]),不需要一瞬加起来 + * 3)最后:背包容量的确定?对数组进行求和,取一半就是背包容量 + * 速度超过61.4%,内存超过5% 24ms + * + * @param nums + * @return + */ + public boolean canPartition(int[] nums) { + int sum = 0; + for (int i = 0; i < nums.length; i++) { + sum += nums[i]; + } + if (sum % 2 != 0) { + return false; + } + //先使用二维dp尝试一次 + int[][] dp = new int[nums.length][sum / 2 + 1]; + + //dp数组的初始化 + //dp[i][0]=0当sum为0时都等于0;dp[0][i] + for (int j = 0; j < dp[0].length; j++) { + if (j >= nums[0]) dp[0][j] = nums[0]; + } + for (int i = 1; i < nums.length; i++) { + for (int j = 1; j < dp[0].length; j++) { + if (j < nums[i]) dp[i][j] = dp[i - 1][j]; + else dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]); + if (dp[i][j] == sum / 2) return true; + } + } + return dp[nums.length - 1][sum / 2] == sum / 2; + } + + + /** + * 自己思路:尝试背包问题的滚动窗口法 + * 速度超过76.11%,内存超过58.54% 22ms + * 时间复杂度O(n^2)空间复杂度O(n) + * for循环优化以后速度超过67.3%,内存超过94.32% + * @param nums + * @return + */ + public boolean canPartition1(int[] nums) { + int sum = 0; + for (int i = 0; i < nums.length; i++) { + sum += nums[i]; + } + if (sum % 2 != 0) { + return false; + } + //先使用二维dp尝试一次 + int target=sum/2; + int[] dp = new int[target + 1]; + + //dp数组的初始化 + //dp[i][0]=0当sum为0时都等于0;dp[0][i] + +// for (int i = 0; i < nums.length; i++) { +// for (int j = dp.length - 1; j >= 1; j--) { +// if(j-nums[i]>=0)dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]); +// } +// } + + 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]); + } + } + return dp[target] == target; + } + + + /** + * 官方代码:与自己的背包滚动窗口类似,但是他是用true和false来判定这次能不能取: + * TODO 动态规划法五部曲分析: + * 1)dp数组的含义分析:dp[i][j]的含义表示为下标[0,i]这个区间里的所有整数,在他们当中是否能够选出一些数,使得这些数之和恰好为整数j + * 2)dp数组的初始化方式:dp[i][0]=true;因为[0,0]这个区间只要所有数都不选,那么就可以 + * 速度击败93.89%,内存击败68.97% 18ms + * @param nums + * @return + */ + public boolean canPartition2(int[] nums) { + int n = nums.length; + if (n < 2) { + return false; + } + int sum = 0, maxNum = 0; + for (int num : nums) { + sum += num; + maxNum = Math.max(maxNum, num); + } + if (sum % 2 != 0) { + return false; + } + int target = sum / 2; + if (maxNum > target) { + return false; + } + boolean[] dp = new boolean[target + 1]; + dp[0] = true; + for (int i = 0; i < n; i++) { + int num = nums[i]; + for (int j = target; j >= num; --j) { + dp[j] |= dp[j - num]; + } + } + return dp[target]; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T09_LastStoneWeightII.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T09_LastStoneWeightII.java new file mode 100644 index 0000000..25a27f4 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T09_LastStoneWeightII.java @@ -0,0 +1,78 @@ +package com.markilue.leecode.dynamic; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: markilue + * @CreateTime: 2022-11-25 13:22 + * @Description: + * TODO 力扣1049题 最后一块石头的重量II: + * 有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 + * 每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下: + * 如果 x == y,那么两块石头都会被完全粉碎; + * 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。 + * 最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。 + * @Version: 1.0 + */ +public class T09_LastStoneWeightII { + + /** + * 自己尝试动态规划法: + * TODO 动态规划五部曲: + * 1)确定dp的含义:使用[0-i]个石头,剩下的重量j的dp[i][j]ture或false + * 2)确定状态转移方程:dp[i][j]= dp[i-1][j-num[i]] || + * 3)确定初始值:dp[0][j==num[0]]=true + * 4)确定遍历方向:for石头再for重量 + * 5)举例说明:当stones = [2,7,4,1,8,1] + * [0 1 2 3 4 5 6 7 8] + * i=0: 0 0 1 0 0 0 0 0 0 + * i=1: 0 0 0 0 0 1 0 0 0 + * i=2: 0 1 0 0 0 1 0 0 0 + * i=3: 1 0 1 0 1 0 1 0 0 + * 推导失败 + * @param stones + * @return + */ + public int lastStoneWeightII(int[] stones) { + return 0; + } + + + /** + * 代码随想录动态规划法:本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。 + * TODO 动态规划五部曲:核心是第六点,本题通过6)将问题转为之前的T08分割等和数组 + * 1)确定dp的含义:dp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背dp[j]这么重的石头。 + * 2)确定状态转移方程::dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]); + * 3)确定初始值:1 <= stones.length <= 30,1 <= stones[i] <= 1000,所以最大重量就是30 * 1000 + * 而我们要求的target其实只是最大重量的一半,所以dp数组开到15000大小就可以了。 + * 4)确定遍历方向:如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历! + * 5)举例说明:当stones = [2,4,1,1] target=4 + * [0 1 2 3 4] + * i=0: 0 0 2 2 2 + * i=1: 0 0 2 2 4 + * i=2: 0 1 2 3 4 + * i=3: 0 1 2 3 4 + * 6)最后dp[target]里是容量为target的背包所能背的最大重量。那么分成两堆石头,一堆石头的总重量是dp[target],另一堆就是sum - dp[target]。 + * 那么相撞之后剩下的最小石头重量就是 (sum - dp[target]) - dp[target]。 + * 速度击败45.51%,内存击败46.8% 时间复杂度O(n*sum) + * @param stones + * @return + */ + public int lastStoneWeightII1(int[] stones) { + int sum = 0; + for (int i : stones) { + sum += i; + } + int target = sum >> 1; + //初始化dp数组 + 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]; + } +}