diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T07_NumTrees.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T07_NumTrees.java new file mode 100644 index 0000000..37616e7 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T07_NumTrees.java @@ -0,0 +1,55 @@ +package com.markilue.leecode.dynamic.second; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic.second + *@Author: markilue + *@CreateTime: 2023-02-15 10:03 + *@Description: + * TODO 力扣96题 不同的二叉搜索树: + * 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 + *@Version: 1.0 + */ +public class T07_NumTrees { + + @Test + public void test() { + int n = 5; + System.out.println(numTrees(n)); + } + + /** + * 思路:本人没有什么思路,找不到状态转移方程,以下是代码随想录思路: + * TODO DP五部曲: + * 1.dp定义:dp[i]表示到i的二叉搜索树有几个 + * 2.dp状态转移方程: + * dp[i]如何得到;可以从1到 i到 n分别做根节点之和;那么以1为根节点,如何得到呢? + * 就是左子树没有节点dp[0]个数;右子树有dp[i-1]个节点个数(从2到n和从1到n-1的搜索数个数是一样的) + * dp[i]+=dp[0]*dp[i-1]+dp[1]*dp[i-2]+...+dp[i-1]*dp[0] + * 3.dp初始化: dp[0]=1;dp[1]=1 + * 4.dp遍历顺序:从前往后 + * 5.dp举例推导:以5为例 + * i=2: 2 + * i=3: 1*2+1*1+2*1=5 + * i=4: 1*5+1*2+2*1+5*1=14 + * i=5: 1*14+1*5+2*2*5*1+14*1=42 + * 速度击败100%,内存击败38.1% + * @param n + * @return + */ + public int numTrees(int n) { + if (n < 2) return 1; + int[] dp = new int[n + 1]; + dp[0] = 1; + dp[1] = 1; + for (int i = 2; i <= n; i++) { + for (int j = 0; j < i; j++) { + dp[i] += dp[j] * dp[i - 1 - j]; + } + } + return dp[n]; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T08_CanPartition.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T08_CanPartition.java new file mode 100644 index 0000000..29f0f0b --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T08_CanPartition.java @@ -0,0 +1,94 @@ +package com.markilue.leecode.dynamic.second; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic.second + *@Author: markilue + *@CreateTime: 2023-02-15 11:27 + *@Description: + * TODO 力扣416题 分割等和子集: + * 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 + *@Version: 1.0 + */ +public class T08_CanPartition { + + @Test + public void test() { +// int[] nums = {1, 2, 3, 5}; + int[] nums = {1, 5, 11, 5}; + System.out.println(canPartition1(nums)); + } + + /** + * 思路:先求出总和,然后挨个遍历,可以选择要当前数;或者不要当前数 + * TODO 动规五部曲: + * 1.dp定义: dp[i][j]表示使用nums[0-i]能不能凑出j这个数 + * 2.dp状态转移方程: + * dp[i][j] :可以选择要当前这个数 或 不要当前这个数(j-nums[i]这个数之前能不能凑出来) + * dp[i][j]=dp[i-1][j]||dp[i-1][j-nums[i]] + * 3.dp初始化:dp[i][0]=true; dp[0][j==nums[0]]=true; + * 4.dp遍历顺序:从上到下;从前往后 + * 5.dp举例推导: + * 速度击败9.4% 内存击败19.39% 53ms + * @param nums + * @return + */ + public boolean canPartition(int[] nums) { + + //求和 + int total = 0; + for (int num : nums) { + total += num; + } + if (total % 2 != 0) return false; + boolean[][] dp = new boolean[nums.length][total / 2 + 1]; + for (int i = 0; i < dp.length; i++) { + dp[i][0] = true; + } + for (int i = 0; i < dp[0].length; i++) { + dp[0][i] = nums[0] == i; + } + + 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] = dp[i - 1][j] || dp[i - 1][j - nums[i]]; + } + } + return dp[nums.length - 1][total / 2]; + + } + + + /** + * 一维dp优化 + * 速度击败69.11% 内存击败51.4% 24ms + * @param nums + * @return + */ + public boolean canPartition1(int[] nums) { + //求和 + int total = 0; + int max = 0; + for (int num : nums) { + total += num; + max = Math.max(max, num); + } + if (total % 2 != 0) return false; + int target = total / 2; + if (max > target) return false;//怎么也不可能到了,剪枝 + boolean[] dp = new boolean[target + 1]; + dp[0] = true; + + + for (int i = 0; i < nums.length; i++) { + for (int j = dp.length - 1; j >= nums[i]; j--) { + dp[j] = dp[j] || dp[j - nums[i]];//反向遍历避免多次 + } + } + return dp[target ]; + + } +} 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 new file mode 100644 index 0000000..7deadf5 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/second/T09_LastStoneWeightII.java @@ -0,0 +1,80 @@ +package com.markilue.leecode.dynamic.second; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.dynamic.second + *@Author: markilue + *@CreateTime: 2023-02-15 13:53 + *@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 { + + + /** + * 思路:消石头可以理解为总有一些是用来消另一些的,那么可以变为让一堆+去消另一堆- + * -> 让正的那一堆和负的那一堆尽可能相等 ->让里面的其中一堆石头尽可能等于总量的一半 ->和上一题一样的思路 + * 速度击败96.8% 内存击败67.2^% 2ms + * @param stones + * @return + */ + public int lastStoneWeightII(int[] stones) { + + int sum = 0; + for (int stone : stones) { + sum += stone; + } + + int target = sum / 2; + int[] dp = new int[target + 1]; + + for (int i = 0; i < dp.length; i++) { + if (i >= stones[0]) dp[i] = stones[0]; + } + + for (int i = 1; i < stones.length; i++) { + for (int j = dp.length - 1; j >= stones[i]; j--) { + dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]); + } + } + return sum - 2 * dp[target]; + + } + + + /** + * 官方提供的另一种解法:判断是否能取到那个值;能取到就把他减掉 + * @param stones + * @return + */ + public int lastStoneWeightII1(int[] stones) { + int sum = 0; + for (int weight : stones) { + sum += weight; + } + int n = stones.length, m = sum / 2; + boolean[][] dp = new boolean[n + 1][m + 1]; + dp[0][0] = true; + for (int i = 0; i < n; ++i) { + for (int j = 0; j <= m; ++j) { + if (j < stones[i]) { + dp[i + 1][j] = dp[i][j]; + } else { + dp[i + 1][j] = dp[i][j] || dp[i][j - stones[i]]; + } + } + } + for (int j = m;; --j) { + if (dp[n][j]) { + return sum - 2 * j; + } + } + } + +}