LeetCode 343. 整数拆分
题目链接:343. 整数拆分
思路:动规五部曲分析:
- 确定dp数组以及下标的含义
dp[i]表示拆分数字i能得到的最大乘积。 - 确定递推公式
dp[i]最大乘积是怎么得到的呢?
从1开始遍历到j,有两种方式能得到dp[i]
1) j ∗ ( i − j ) j * (i - j) j∗(i−j)相乘直接就是最大的乘积;
2) j ∗ d p [ i − j ] j * dp[i-j] j∗dp[i−j],相当于再继续拆分 ( i − j ) (i - j) (i−j)
也可以这么理解, j ∗ ( i − j ) j * (i - j) j∗(i−j)是直接把整数拆分为两个数相乘,而 j ∗ d p [ i − j ] j * dp[i - j] j∗dp[i−j]是拆分成两个以上的数相乘,所以递推公式为: d p [ i ] = m a x ( d p [ i ] , ( i − j ) ∗ j , d p [ i − j ] ∗ j ) dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j}) dp[i]=max(dp[i],(i−j)∗j,dp[i−j]∗j)。 - dp数组的初始化
因为n>=2的,所以初始化dp[0]和dp[1]其实是没有意义的,初始化dp[2]=1即可。 - 确定遍历顺序
因为dp数组从dp[2](索引3)时才开始有实际意义,所以i从索引4开始从前向后遍历到n。 - 打印dp数组的最后一位即为结果。
Python版本:
class Solution:
def integerBreak(self, n: int) -> int:
if n<=3:
return n-1
dp = [0] * (n+1)
dp[2] = 1
for i in range(3, n+1):
for j in range(1, i//2 + 1):
dp[i] = max(dp[i], dp[i-j]*j, j*(i-j))
return dp[-1]
时间复杂度: O ( n ) O(n) O(n),空间复杂度: O ( n ) O(n) O(n)
go版本:
func integerBreak(n int) int {
if n<=3 {
return n-1
}
dp := make([]int, n+1)
dp[2] = 1
for i:=3; i<=n; i++ {
for j:=1; j<i/2+1; j++ {
dp[i] = max(dp[i], max(dp[i-j]*j, j*(i-j)))
}
}
return dp[len(dp)-1]
}
func max(i, j int) int {
if i>j {
return i
} else {
return j
}
}
LeetCode 96. 不同的二叉搜索树
题目链接:96. 不同的二叉搜索树
思路:动规五部曲分析:
-
确定dp数组以及下标的含义
dp[i]表示 i 个节点组成且节点值从 1 到 i 互不相同的二叉搜索树有 dp[i] 种。 -
确定递推公式
dp[i]是怎么得到的呢?这个问题其实比较抽象,我们如果做这种题一时没有明确的递推公式的思路的话,可以通过枚举法来找找规律。
dp[1] = 1
dp[2] = 2
从n=3开始,有5种二叉搜索树(图摘自代码随想录):
我们发现,以1做根节点的情况有两种,以2做根节点的情况有1种,以3做根节点的情况有两种;
其中:以1为根节点时其右子树的二叉树结构与n=2时的两种二叉树结构相同;以2为根节点时其左、右子树的树结构与n=1时的二叉树结构相同;以3为根节点时其左子树的二叉树结构与n=2时的两种二叉树结构相同。
综上所述,元素1为根结点的二叉搜索树数量 = 左子树有0个元素的搜索树数量 * 右子树有2个元素的搜索树数量;元素2为根结点的二叉搜索树数量 = 左子树有1个元素的搜索树数量 * 右子树有1个元素的搜索树数量;元素3为根结点的二叉搜索树数量 = 左子树有2个元素的搜索树数量 * 右子树有0个元素的搜索树数量。三种情况的数量之和就是dp[3]。
所以, d p [ 3 ] = d p [ 0 ] ∗ d p [ 2 ] + d p [ 1 ] ∗ d p [ 1 ] + d p [ 2 ] ∗ d p [ 0 ] dp[3] = dp[0] * dp[2] + dp[1] * dp[1] + dp[2] * dp[0] dp[3]=dp[0]∗dp[2]+dp[1]∗dp[1]+dp[2]∗dp[0]
如果 j 作为根节点的元素, d p [ i ] = d p [ 0 ] ∗ d p [ i − 1 ] + d p [ 1 ] ∗ d p [ i − 2 ] + . . . + d p [ j − 1 ] ∗ d p [ i − j ] + . . . + d p [ i − 2 ] ∗ d p [ 1 ] + d p [ i − 1 ] ∗ d p [ 0 ] dp[i] = dp[0] * dp[i-1] + dp[1] * dp[i-2] + ... + dp[j-1] * dp[i-j] + ...+ dp[i-2] * dp[1] + dp[i-1] * dp[0] dp[i]=dp[0]∗dp[i−1]+dp[1]∗dp[i−2]+...+dp[j−1]∗dp[i−j]+...+dp[i−2]∗dp[1]+dp[i−1]∗dp[0]
也就是说,j 从1开始遍历到 i,dp[i] 的递推公式为: d p [ i ] + = d p [ j − 1 ] ∗ d p [ i − j ] dp[i] += dp[j-1] * dp[i-j] dp[i]+=dp[j−1]∗dp[i−j]。 -
dp数组的初始化
本题在初始化的时候还有一个细节,那就是当n=0时 dp[0] 也是有意义的,因为空节点也是一个二叉搜索树,所以 d p [ 0 ] = 1 dp[0]=1 dp[0]=1。 -
确定遍历顺序
dp数组从i = 1,从前向后遍历到n。 -
打印dp数组的最后一位即为结果。
Python版本:
class Solution:
def numTrees(self, n: int) -> int:
if n<=2:
return n
dp = [0] * (n+1)
dp[0] = 1
for i in range(1, n+1):
for j in range(1, i+1):
dp[i] += dp[j-1]*dp[i-j]
return dp[-1]
时间复杂度: O ( n ) O(n) O(n),空间复杂度: O ( n ) O(n) O(n)
go版本:
func numTrees(n int) int {
if n<=2 {
return n
}
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[len(dp)-1]
}