diff --git a/problems/0096.不同的二叉搜索树.md b/problems/0096.不同的二叉搜索树.md index d086b1f3..519fd323 100644 --- a/problems/0096.不同的二叉搜索树.md +++ b/problems/0096.不同的二叉搜索树.md @@ -69,7 +69,7 @@ dp[3],就是 元素1为头结点搜索树的数量 + 元素2为头结点搜索 **dp[i] : 1到i为节点组成的二叉搜索树的个数为dp[i]**。 -也可以理解是i的不同元素节点组成的二叉搜索树的个数为dp[i] ,都是一样的。 +也可以理解是i个不同元素节点组成的二叉搜索树的个数为dp[i] ,都是一样的。 以下分析如果想不清楚,就来回想一下dp[i]的定义 @@ -199,11 +199,11 @@ class Solution: ### Go ```Go func numTrees(n int)int{ - dp:=make([]int,n+1) - dp[0]=1 - for i:=1;i<=n;i++{ - for j:=1;j<=i;j++{ - dp[i]+=dp[j-1]*dp[i-j] + dp := make([]int, n+1) + dp[0] = 1 + for i := 1; i <= n; i++ { + for j := 1; j <= i; j++ { + dp[i] += dp[j-1] * dp[i-j] } } return dp[n] diff --git a/problems/0279.完全平方数.md b/problems/0279.完全平方数.md index 50ddf5f9..b6a97be3 100644 --- a/problems/0279.完全平方数.md +++ b/problems/0279.完全平方数.md @@ -235,43 +235,6 @@ class Solution: return dp[n] ``` -Python3: -```python -class Solution: - '''版本一,先遍历背包, 再遍历物品''' - def numSquares(self, n: int) -> int: - dp = [n] * (n + 1) - dp[0] = 0 - # 遍历背包 - for j in range(1, n+1): - for i in range(1, n): - num = i ** 2 - if num > j: break - # 遍历物品 - if j - num >= 0: - dp[j] = min(dp[j], dp[j - num] + 1) - return dp[n] - -class Solution: - '''版本二, 先遍历物品, 再遍历背包''' - def numSquares(self, n: int) -> int: - # 初始化 - # 组成和的完全平方数的最多个数,就是只用1构成 - # 因此,dp[i] = i - dp = [i for i in range(n + 1)] - # dp[0] = 0 无意义,只是为了方便记录特殊情况: - # n本身就是完全平方数,dp[n] = min(dp[n], dp[n - n] + 1) = 1 - - for i in range(1, n): # 遍历物品 - if i * i > n: - break - num = i * i - for j in range(num, n + 1): # 遍历背包 - dp[j] = min(dp[j], dp[j - num] + 1) - - return dp[n] -``` - Go: ```go // 版本一,先遍历物品, 再遍历背包 diff --git a/problems/0416.分割等和子集.md b/problems/0416.分割等和子集.md index 52b48264..ecee1d83 100644 --- a/problems/0416.分割等和子集.md +++ b/problems/0416.分割等和子集.md @@ -32,7 +32,7 @@ ## 思路 -这道题目初步看,是如下两题几乎是一样的,大家可以用回溯法,解决如下两题 +这道题目初步看,和如下两题几乎是一样的,大家可以用回溯法,解决如下两题 * 698.划分为k个相等的子集 * 473.火柴拼正方形 @@ -62,7 +62,7 @@ 回归主题:首先,本题要求集合里能否出现总和为 sum / 2 的子集。 -那么来一一对应一下本题,看看背包问题如果来解决。 +那么来一一对应一下本题,看看背包问题如何来解决。 **只有确定了如下四点,才能把01背包问题套到本题上来。** @@ -77,9 +77,9 @@ 1. 确定dp数组以及下标的含义 -01背包中,dp[j] 表示: 容量为j的背包,所背的物品价值可以最大为dp[j]。 +01背包中,dp[j] 表示: 容量为j的背包,所背的物品价值最大可以为dp[j]。 -本题中每一个元素的数值即是重量,也是价值。 +本题中每一个元素的数值既是重量,也是价值。 **套到本题,dp[j]表示 背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]**。 @@ -106,9 +106,9 @@ 从dp[j]的定义来看,首先dp[0]一定是0。 -如果如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。 +如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。 -**这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了**。 +**这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了**。 本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了。 @@ -202,15 +202,15 @@ class Solution { if(nums == null || nums.length == 0) return false; int n = nums.length; int sum = 0; - for(int num : nums){ + 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--){ + 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]); } @@ -220,6 +220,7 @@ class Solution { } ``` +二维数组版本(易于理解): ```java public class Solution { public static void main(String[] args) { @@ -288,46 +289,6 @@ false true false false false true true false false false false true false true false false false true true false false false true true ``` - -二维数组版本(易于理解): -```Java -class Solution { - public boolean canPartition(int[] nums) { - int sum = 0; - for (int i = 0; i < nums.length; i++) { - sum += nums[i]; - } - - if (sum % 2 == 1) - return false; - int target = sum / 2; - - //dp[i][j]代表可装物品为0-i,背包容量为j的情况下,背包内容量的最大价值 - int[][] dp = new int[nums.length][target + 1]; - - //初始化,dp[0][j]的最大价值nums[0](if j > weight[i]) - //dp[i][0]均为0,不用初始化 - for (int j = nums[0]; j <= target; j++) { - dp[0][j] = nums[0]; - } - - //遍历物品,遍历背包 - //递推公式: - for (int i = 1; i < nums.length; i++) { - for (int j = 0; j <= target; j++) { - //背包容量可以容纳nums[i] - if (j >= nums[i]) { - dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]); - } else { - dp[i][j] = dp[i - 1][j]; - } - } - } - - return dp[nums.length - 1][target] == target; - } -} -``` ### Python: ```python class Solution: @@ -341,6 +302,7 @@ class Solution: dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]) return target == dp[target] ``` + ### Go: ```go // 分割等和子集 动态规划 @@ -369,46 +331,6 @@ func canPartition(nums []int) bool { } ``` -```go -func canPartition(nums []int) bool { - /** - 动态五部曲: - 1.确定dp数组和下标含义 - 2.确定递推公式 - 3.dp数组初始化 - 4.dp遍历顺序 - 5.打印 - **/ - //确定和 - var sum int - for _,v:=range nums{ - sum+=v - } - if sum%2!=0{ //如果和为奇数,则不可能分成两个相等的数组 - return false - } - sum/=2 - //确定dp数组和下标含义 - var dp [][]bool //dp[i][j] 表示: 前i个石头是否总和不大于J - //初始化数组 - dp=make([][]bool,len(nums)+1) - for i,_:=range dp{ - dp[i]=make([]bool,sum+1) - dp[i][0]=true - } - for i:=1;i<=len(nums);i++{ - for j:=1;j<=sum;j++{//j是固定总量 - if j>=nums[i-1]{//如果容量够用则可放入背包 - dp[i][j]=dp[i-1][j]||dp[i-1][j-nums[i-1]] - }else{//如果容量不够用则不拿,维持前一个状态 - dp[i][j]=dp[i-1][j] - } - } - } - return dp[len(nums)][sum] -} -``` - ### javaScript: ```js diff --git a/problems/0474.一和零.md b/problems/0474.一和零.md index 45a0a270..213a2aa2 100644 --- a/problems/0474.一和零.md +++ b/problems/0474.一和零.md @@ -59,7 +59,7 @@ 但本题其实是01背包问题! -这不过这个背包有两个维度,一个是m 一个是n,而不同长度的字符串就是不同大小的待装物品。 +只不过这个背包有两个维度,一个是m 一个是n,而不同长度的字符串就是不同大小的待装物品。 开始动规五部曲: @@ -114,7 +114,7 @@ for (string str : strs) { // 遍历物品 有同学可能想,那个遍历背包容量的两层for循环先后循序有没有什么讲究? -没讲究,都是物品重量的一个维度,先遍历那个都行! +没讲究,都是物品重量的一个维度,先遍历哪个都行! 5. 举例推导dp数组 @@ -152,7 +152,7 @@ public: ## 总结 -不少同学刷过这道提,可能没有总结这究竟是什么背包。 +不少同学刷过这道题,可能没有总结这究竟是什么背包。 此时我们讲解了0-1背包的多种应用, @@ -252,53 +252,6 @@ func max(a,b int) int { return b } ``` -> 传统背包,三维数组法 -```golang -func findMaxForm(strs []string, m int, n int) int { - //dp的第一个index代表项目的多少,第二个代表的是背包的容量 - //所以本处项目的多少是len(strs),容量为m和n - dp:=make([][][]int,len(strs)+1) - for i:=0;i<=len(strs);i++{ - //初始化背包容量 - strDp:=make([][]int,m+1) - for j:=0;j=zero&&j>=one{ - dp[k][i][j]=getMax(dp[k-1][i][j],dp[k-1][i-zero][j-one]+1) - } - } - } - } - return dp[len(strs)][m][n] -} -func getMax(a,b int)int{ - if a>b{ - return a - } - return b -} -``` ### Javascript ```javascript diff --git a/problems/0494.目标和.md b/problems/0494.目标和.md index 946e52f2..6c8c28ec 100644 --- a/problems/0494.目标和.md +++ b/problems/0494.目标和.md @@ -123,7 +123,7 @@ public: x = (target + sum) / 2 -**此时问题就转化为,装满容量为x背包,有几种方法**。 +**此时问题就转化为,装满容量为x的背包,有几种方法**。 这里的x,就是bagSize,也就是我们后面要求的背包容量。 @@ -184,11 +184,11 @@ dp[j] += dp[j - nums[i]] 3. dp数组如何初始化 -从递归公式可以看出,在初始化的时候dp[0] 一定要初始化为1,因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递归结果将都是0。 +从递推公式可以看出,在初始化的时候dp[0] 一定要初始化为1,因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递推结果将都是0。 这里有录友可能认为从dp数组定义来说 dp[0] 应该是0,也有录友认为dp[0]应该是1。 -其实不要硬去解释它的含义,咱就把 dp[0]的情况带入本题看看就是应该等于多少。 +其实不要硬去解释它的含义,咱就把 dp[0]的情况带入本题看看应该等于多少。 如果数组[0] ,target = 0,那么 bagSize = (target + sum) / 2 = 0。 dp[0]也应该是1, 也就是说给数组里的元素 0 前面无论放加法还是减法,都是 1 种方法。 @@ -198,7 +198,7 @@ dp[j] += dp[j - nums[i]] 其实 此时最终的dp[0] = 32,也就是这五个零 子集的所有组合情况,但此dp[0]非彼dp[0],dp[0]能算出32,其基础是因为dp[0] = 1 累加起来的。 -dp[j]其他下标对应的数值也应该初始化为0,从递归公式也可以看出,dp[j]要保证是0的初始值,才能正确的由dp[j - nums[i]]推导出来。 +dp[j]其他下标对应的数值也应该初始化为0,从递推公式也可以看出,dp[j]要保证是0的初始值,才能正确的由dp[j - nums[i]]推导出来。 4. 确定遍历顺序 @@ -245,7 +245,7 @@ public: ## 总结 -此时 大家应该不仅想起,我们之前讲过的[回溯算法:39. 组合总和](https://programmercarl.com/0039.组合总和.html)是不是应该也可以用dp来做啊? +此时 大家应该不禁想起,我们之前讲过的[回溯算法:39. 组合总和](https://programmercarl.com/0039.组合总和.html)是不是应该也可以用dp来做啊? 是的,如果仅仅是求个数的话,就可以用dp,但[回溯算法:39. 组合总和](https://programmercarl.com/0039.组合总和.html)要求的是把所有组合列出来,还是要使用回溯法爆搜的。