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]**
也可以理解是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]

View File

@ -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
// 版本一,先遍历物品, 再遍历背包

View File

@ -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

View File

@ -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代表项目的多少第二个代表的是背包的容量
//所以本处项目的多少是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

View File

@ -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)要求的是把所有组合列出来,还是要使用回溯法爆搜的。