diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T09_LastStoneWeightII.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T09_LastStoneWeightII.java index 7deadf5..943d609 100644 --- a/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T09_LastStoneWeightII.java +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T09_LastStoneWeightII.java @@ -1,5 +1,7 @@ package com.markilue.leecode.dynamic.second; +import org.junit.Test; + /** *@BelongsProject: Leecode *@BelongsPackage: com.markilue.leecode.dynamic.second @@ -16,6 +18,14 @@ package com.markilue.leecode.dynamic.second; */ public class T09_LastStoneWeightII { + @Test + public void test() { + String str1 = new String("1") + new String("1"); + str1.intern(); + String str2 = "11"; + System.out.println(str1 == str2); + } + /** * 思路:消石头可以理解为总有一些是用来消另一些的,那么可以变为让一堆+去消另一堆- @@ -70,7 +80,7 @@ public class T09_LastStoneWeightII { } } } - for (int j = m;; --j) { + for (int j = m; ; --j) { if (dp[n][j]) { return sum - 2 * j; } diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T10_FindTargetSumWays.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T10_FindTargetSumWays.java new file mode 100644 index 0000000..72b2ea3 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T10_FindTargetSumWays.java @@ -0,0 +1,124 @@ +package com.markilue.leecode.dynamic.second; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic.second + *@Author: markilue + *@CreateTime: 2023-02-16 10:20 + *@Description: + * TODO 力扣394题 目标和: + * 给你一个整数数组 nums 和一个整数 target 。 + * 向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 : + * 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。 + * 返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。 + * 1 <= nums.length <= 20 + * 0 <= nums[i] <= 1000 + * 0 <= sum(nums[i]) <= 1000 + * -1000 <= target <= 1000 + *@Version: 1.0 + */ +public class T10_FindTargetSumWays { + + @Test + public void test() { + int[] nums = {100}; + int target = -200; + System.out.println(findTargetSumWays(nums, target)); + } + + /** + * 思路:可以计算出sum出来,然后有正的一半,有负的一半,那么x-(sum-x)=target;x=(sum+target)/2;所以又转为了背包问题 + * TODO DP五部曲: + * 1.dp定义:dp[i][j]表示使用nums[0-i]能凑出j的方法数 + * 2.dp状态转移方程: + * 1.dp[i][j]有两种方式得到: + * 1)使用当前的数nums[i]: dp[i-1][j-nums[i]] + * 2)不使用当前的数nums[i]: dp[i-1][j] + * dp[i][j]=dp[i-1][j-nums[i]]+dp[i-1][j] + * 3.dp初始化:dp[i][0]=1 dp[0][j==nums[0]]=1 + * 4.dp遍历顺序:从上到下 从前往后 + * 5.dp举例推导: + * 速度击败67.3% 内存击败12% + * @param nums + * @param target + * @return + */ + public int findTargetSumWays(int[] nums, int target) { + +// if (nums.length == 1) { +// return nums[0] == target ? 1 : 0; +// } + + int sum = 0; + for (int num : nums) { + sum += num; + } + if ((sum + target) % 2 != 0) return 0; + if ((sum + target) < 0) return 0;//注意条件:-1000 <= target <= 1000 + int need = (sum + target) / 2; + + int[][] dp = new int[nums.length][need + 1]; + + //初始化 + for (int i = 0; i < dp.length; i++) { + dp[i][0] = 1; + } + for (int i = 0; i < dp[0].length; i++) { + if (i == nums[0]) { + dp[0][i] += 1; + break; + } + } + + for (int i = 1; i < dp.length; i++) { + for (int j = 0; j < dp[0].length; j++) { + if (j >= nums[i]) dp[i][j] = dp[i - 1][j - nums[i]] + dp[i - 1][j]; + else dp[i][j] = dp[i - 1][j]; + } + } + return dp[dp.length - 1][need]; + + + } + + + /** + * 一维dp优化 + * 速度击败94.6% 内存击败42.53% 2ms + * @param nums + * @param target + * @return + */ + public int findTargetSumWays1(int[] nums, int target) { + + int sum = 0; + for (int num : nums) { + sum += num; + } + if ((sum + target) % 2 != 0) return 0; + if ((sum + target) < 0) return 0;//注意条件:-1000 <= target <= 1000 + int need = (sum + target) / 2; + + int[] dp = new int[need + 1]; + + //初始化 + dp[0] = 1; + for (int i = 0; i < dp.length; i++) { + if (i == nums[0]) { + dp[i] += 1; + break; + } + } + + for (int i = 1; i < nums.length; i++) { + for (int j = dp.length - 1; j >= nums[i]; j--) { + dp[j] = dp[j - nums[i]] + dp[j]; + } + } + return dp[need]; + + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T11_FindMaxForm.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T11_FindMaxForm.java new file mode 100644 index 0000000..9d87c27 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T11_FindMaxForm.java @@ -0,0 +1,209 @@ +package com.markilue.leecode.dynamic.second; + +import org.junit.Test; + +import java.util.Arrays; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic.second + *@Author: markilue + *@CreateTime: 2023-02-16 10:58 + *@Description: + * TODO 力扣474题 一和零: + * 给你一个二进制字符串数组 strs 和两个整数 m 和 n 。 + * 请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。 + * 如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。 + *@Version: 1.0 + */ +public class T11_FindMaxForm { + + @Test + public void test() { + String[] strs = {"10", "0", "1"}; + int m = 1, n = 1; + System.out.println(findMaxForm(strs, m, n)); + } + + /** + * 思路:可以看做是有两个限制的背包: + * TODO DP五部曲: + * 1.dp定义:dp[i][j][k]表示使用strs[0-i]而背包大小为j和k时,最多的数量 + * 2.dp状态转移方程: + * 1.dp[i][j][k]可以使用str[i]或者不适用str[i] + * dp[i][j][k]=max(dp[i-1][j][k],dp[i-1][j-str[i 0]][k-str[i `0`]]+1) + * 3.dp初始化:dp[i][0][k]=0;dp[i][j][0]=0;dp[0][j][k]=1; + * 4.dp遍历顺序: + * 速度击败6.33% 内存击败5.2% 64ms + * @param strs + * @param m + * @param n + * @return + */ + public int findMaxForm(String[] strs, int m, int n) { + + //记录每个zero,one个数 + int[][] nums = new int[strs.length][2]; + int count = -1; + for (String str : strs) { + int zero = 0; + int one = 0; + for (char c : str.toCharArray()) { + if (c == '0') zero++; + else one++; + } + nums[++count] = new int[]{zero, one}; + } + + //初始化 + int[][][] dp = new int[strs.length][m + 1][n + 1]; + for (int i = nums[0][0]; i < m + 1; i++) { + for (int j = nums[0][1]; j < n + 1; j++) { + dp[0][i][j] = 1; + } + } + + + for (int i = 1; i < dp.length; i++) { + for (int j = 0; j < dp[0].length; j++) { + if (j < nums[i][0]) dp[i][j] = dp[i - 1][j]; + else { + for (int k = 0; k < dp[0][0].length; k++) { + if (k < nums[i][1]) dp[i][j][k] = dp[i - 1][j][k]; + else dp[i][j][k] = Math.max(dp[i - 1][j][k], dp[i - 1][j - nums[i][0]][k - nums[i][1]] + 1); + } + } + } + } + return dp[dp.length - 1][m][n]; + + + } + + + /** + * 二维dp优化 + * 速度击败30.23% 内存击败98.69% 27ms + * @param strs + * @param m + * @param n + * @return + */ + public int findMaxForm1(String[] strs, int m, int n) { + + //记录每个zero,one个数 + int[][] nums = new int[strs.length][2]; + int count = -1; + for (String str : strs) { + int zero = 0; + int one = 0; + for (char c : str.toCharArray()) { + if (c == '0') zero++; + else one++; + } + nums[++count] = new int[]{zero, one}; + } + + //初始化 + int[][] dp = new int[m + 1][n + 1]; + for (int i = nums[0][0]; i < m + 1; i++) { + for (int j = nums[0][1]; j < n + 1; j++) { + dp[i][j] = 1; + } + } + + + for (int i = 1; i < strs.length; i++) { + for (int j = dp.length - 1; j >= nums[i][0]; j--) { + for (int k = dp[0].length - 1; k >= nums[i][1]; k--) { + dp[j][k] = Math.max(dp[j][k], dp[j - nums[i][0]][k - nums[i][1]] + 1); + } + } + } + return dp[m][n]; + + + } + + + /** + * 进一步优化,少for循环求nums一次 + * 速度击败97.12% 内存击败49.46% 16ms + * @param strs + * @param m + * @param n + * @return + */ + public int findMaxForm2(String[] strs, int m, int n) { + + int[][] dp = new int[m + 1][n + 1]; + int length = strs.length; + for (int i = 0; i < length; i++) { + int[] zerosOnes = getZerosOnes(strs[i]); + int zeros = zerosOnes[0], ones = zerosOnes[1]; + for (int j = m; j >= zeros; j--) { + for (int k = n; k >= ones; k--) { + dp[j][k] = Math.max(dp[j][k], dp[j - zeros][k - ones] + 1); + } + } + } + return dp[m][n]; + + + } + + public int[] getZerosOnes(String str) { + int[] zerosOnes = new int[2]; + int length = str.length(); + for (int i = 0; i < length; i++) { + zerosOnes[str.charAt(i) - '0']++; + } + return zerosOnes; + } + + + /** + * 官方最快:回溯 + * 速度击败100% 内存击败99.84% 6ms + */ + int max = 0; + public int findMaxForm3(String[] strs, int m, int n) { + int len = strs.length; + int[][] counts = new int[len][2]; + for(int i = 0; i < len; i++) { + counts[i] = count(strs[i]); + } + Arrays.sort(counts, (a, b) -> a[0] == b[0] ? a[1] - b[1] : a[0] - b[0]); + backTrace(counts, 0, m, n, 0); + return max; + } + + private void backTrace(int[][] counts, int index, int m, int n, int currLength) { + if(m < 0 || n < 0) return; + for(int i = index; i < counts.length; i++) { + int[] count = counts[i]; + if(i > index && count[0] == counts[i-1][0]) continue; + if(count[0] <= m && count[1] <= n) { + max = Math.max(max, currLength + 1); + backTrace(counts, i + 1, m - count[0], n - count[1], currLength + 1); + } + } + } + + private int[] count(String s) { + int oneCount = 0; + int zeroCount = 0; + int i = s.length() - 1; + while(i >= 0) { + char sc = s.charAt(i); + if(sc == '1') { + oneCount++; + } + else { + zeroCount++; + } + i--; + } + return new int[]{zeroCount,oneCount}; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T12_Change.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T12_Change.java new file mode 100644 index 0000000..a1a16ce --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T12_Change.java @@ -0,0 +1,87 @@ +package com.markilue.leecode.dynamic.second; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic.second + *@Author: markilue + *@CreateTime: 2023-02-16 11:54 + *@Description: + * TODO 力扣518题 零钱兑换II: + * 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 + * 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 + * 假设每一种面额的硬币有无限个。 + * 题目数据保证结果符合 32 位带符号整数。 + *@Version: 1.0 + */ +public class T12_Change { + + @Test + public void test() { + int amount = 500; + int[] coins = {2, 7, 13}; + System.out.println(change(amount, coins)); + } + + /** + * 思路:完全背包问题 -> 可以重复使用coins + * TODO DP五部曲: + * 1.dp含义:dp[i][j]表示使用coins[0-i]能凑出coins的方式 + * 2.dp状态转移方程:dp[i][j]有两种方式凑出 : 使用coins[i] 不使用coins[i] + * dp[i][j]=dp[i-1][j]+dp[i][j-coins[i]] + * 3.dp初始化:dp[i][0]=1 dp[0][j%coins[0]==0]=1 + * 4.dp遍历顺序:先遍历coins,再遍历背包:因为求的是组合数 + * 5.dp举例推导: + * 速度击败26.38% 内存击败19.51% + * @param amount + * @param coins + * @return + */ + public int change(int amount, int[] coins) { + + int[][] dp = new int[coins.length][amount + 1]; + + for (int i = 0; i < dp.length; i++) { + dp[i][0] = 1; + } + for (int i = 1; i < dp[0].length; i++) { + if (i % coins[0] == 0) dp[0][i] = 1; + } + + for (int i = 1; i < dp.length; i++) { + for (int j = 1; j < dp[0].length; j++) { + if (j < coins[i]) dp[i][j] = dp[i - 1][j]; + else dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i]]; + } + } + return dp[coins.length - 1][amount]; + + } + + + /** + * 一维dp优化 + * 速度击败41.89% 内存击败54.24% + * @param amount + * @param coins + * @return + */ + public int change1(int amount, int[] coins) { + + int[] dp = new int[amount + 1]; + + + for (int i = 0; i < dp.length; i++) { + if (i % coins[0] == 0) dp[i] = 1; + } + + for (int i = 1; i < coins.length; i++) { + for (int j = 1; j < dp.length; j++) { + if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]]; + } + } + return dp[amount]; + + } +}