Merge pull request #1830 from juguagua/leetcode-modify-the-code-of-the-dp

更新动态规划部分:从 “不同的二叉搜索树” 到 “完全平方数”
This commit is contained in:
程序员Carl
2022-12-22 10:52:51 +08:00
committed by GitHub
5 changed files with 25 additions and 187 deletions

View File

@ -69,7 +69,7 @@ dp[3],就是 元素1为头结点搜索树的数量 + 元素2为头结点搜索
**dp[i] 1到i为节点组成的二叉搜索树的个数为dp[i]** **dp[i] 1到i为节点组成的二叉搜索树的个数为dp[i]**
也可以理解是i不同元素节点组成的二叉搜索树的个数为dp[i] ,都是一样的。 也可以理解是i不同元素节点组成的二叉搜索树的个数为dp[i] ,都是一样的。
以下分析如果想不清楚就来回想一下dp[i]的定义 以下分析如果想不清楚就来回想一下dp[i]的定义

View File

@ -235,43 +235,6 @@ class Solution:
return dp[n] 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
```go ```go
// 版本一,先遍历物品, 再遍历背包 // 版本一,先遍历物品, 再遍历背包

View File

@ -32,7 +32,7 @@
## 思路 ## 思路
这道题目初步看,如下两题几乎是一样的,大家可以用回溯法,解决如下两题 这道题目初步看,如下两题几乎是一样的,大家可以用回溯法,解决如下两题
* 698.划分为k个相等的子集 * 698.划分为k个相等的子集
* 473.火柴拼正方形 * 473.火柴拼正方形
@ -62,7 +62,7 @@
回归主题:首先,本题要求集合里能否出现总和为 sum / 2 的子集。 回归主题:首先,本题要求集合里能否出现总和为 sum / 2 的子集。
那么来一一对应一下本题,看看背包问题如来解决。 那么来一一对应一下本题,看看背包问题如来解决。
**只有确定了如下四点才能把01背包问题套到本题上来。** **只有确定了如下四点才能把01背包问题套到本题上来。**
@ -77,9 +77,9 @@
1. 确定dp数组以及下标的含义 1. 确定dp数组以及下标的含义
01背包中dp[j] 表示: 容量为j的背包所背的物品价值可以最大为dp[j]。 01背包中dp[j] 表示: 容量为j的背包所背的物品价值最大可以为dp[j]。
本题中每一个元素的数值是重量,也是价值。 本题中每一个元素的数值是重量,也是价值。
**套到本题dp[j]表示 背包总容量所能装的总重量是j放进物品后背的最大重量为dp[j]** **套到本题dp[j]表示 背包总容量所能装的总重量是j放进物品后背的最大重量为dp[j]**
@ -106,9 +106,9 @@
从dp[j]的定义来看首先dp[0]一定是0。 从dp[j]的定义来看首先dp[0]一定是0。
如果如果题目给的价值都是正整数那么非0下标都初始化为0就可以了如果题目给的价值有负数那么非0下标就要初始化为负无穷。 如果题目给的价值都是正整数那么非0下标都初始化为0就可以了如果题目给的价值有负数那么非0下标就要初始化为负无穷。
**这样才能让dp数组在递归公式的过程中取最大的价值,而不是被初始值覆盖了** **这样才能让dp数组在递的过程中取最大的价值,而不是被初始值覆盖了**
本题题目中 只包含正整数的非空数组所以非0下标的元素初始化为0就可以了。 本题题目中 只包含正整数的非空数组所以非0下标的元素初始化为0就可以了。
@ -220,6 +220,7 @@ class Solution {
} }
``` ```
二维数组版本(易于理解):
```java ```java
public class Solution { public class Solution {
public static void main(String[] args) { 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 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
```python ```python
class Solution: class Solution:
@ -341,6 +302,7 @@ class Solution:
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]) dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
return target == dp[target] return target == dp[target]
``` ```
### Go ### Go
```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: ### javaScript:
```js ```js

View File

@ -59,7 +59,7 @@
但本题其实是01背包问题 但本题其实是01背包问题
不过这个背包有两个维度一个是m 一个是n而不同长度的字符串就是不同大小的待装物品。 不过这个背包有两个维度一个是m 一个是n而不同长度的字符串就是不同大小的待装物品。
开始动规五部曲: 开始动规五部曲:
@ -114,7 +114,7 @@ for (string str : strs) { // 遍历物品
有同学可能想那个遍历背包容量的两层for循环先后循序有没有什么讲究 有同学可能想那个遍历背包容量的两层for循环先后循序有没有什么讲究
没讲究,都是物品重量的一个维度,先遍历个都行! 没讲究,都是物品重量的一个维度,先遍历个都行!
5. 举例推导dp数组 5. 举例推导dp数组
@ -152,7 +152,7 @@ public:
## 总结 ## 总结
不少同学刷过这道,可能没有总结这究竟是什么背包。 不少同学刷过这道,可能没有总结这究竟是什么背包。
此时我们讲解了0-1背包的多种应用 此时我们讲解了0-1背包的多种应用
@ -252,53 +252,6 @@ func max(a,b int) int {
return b return b
} }
``` ```
> 传统背包,三维数组法
```golang
func findMaxForm(strs []string, m int, n int) int {
//dp的第一个index代表项目的多少第二个代表的是背包的容量
//所以本处项目的多少是lenstrs容量为m和n
dp:=make([][][]int,len(strs)+1)
for i:=0;i<=len(strs);i++{
//初始化背包容量
strDp:=make([][]int,m+1)
for j:=0;j<m+1;j++{
tmp:=make([]int,n+1)
strDp[j]=tmp
}
dp[i]=strDp
}
for k,value:=range strs{
//统计每个字符串0和1的个数
var zero,one int
for _,v:=range value{
if v=='0'{
zero++
}else{
one++
}
}
k+=1
//计算dp
for i:=0;i<=m;i++{
for j:=0;j<=n;j++{
//如果装不下
dp[k][i][j]=dp[k-1][i][j]
//如果装的下
if i>=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
```javascript ```javascript

View File

@ -123,7 +123,7 @@ public:
x = (target + sum) / 2 x = (target + sum) / 2
**此时问题就转化为装满容量为x背包有几种方法** **此时问题就转化为装满容量为x背包,有几种方法**
这里的x就是bagSize也就是我们后面要求的背包容量。 这里的x就是bagSize也就是我们后面要求的背包容量。
@ -184,11 +184,11 @@ dp[j] += dp[j - nums[i]]
3. dp数组如何初始化 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数组定义来说 dp[0] 应该是0也有录友认为dp[0]应该是1。
其实不要硬去解释它的含义,咱就把 dp[0]的情况带入本题看看就是应该等于多少。 其实不要硬去解释它的含义,咱就把 dp[0]的情况带入本题看看应该等于多少。
如果数组[0] target = 0那么 bagSize = (target + sum) / 2 = 0。 dp[0]也应该是1 也就是说给数组里的元素 0 前面无论放加法还是减法,都是 1 种方法。 如果数组[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[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. 确定遍历顺序 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)要求的是把所有组合列出来,还是要使用回溯法爆搜的。 是的如果仅仅是求个数的话就可以用dp但[回溯算法39. 组合总和](https://programmercarl.com/0039.组合总和.html)要求的是把所有组合列出来,还是要使用回溯法爆搜的。