LeetCode 343 整数拆分
题目链接:343. 整数拆分 - 力扣(Leetcode)
将整数拆分,使得拆分结果的乘积最大化,一开始碰到类似的题目,很难想象应该拆分为多少个数,容易想复杂。但其实对于一个数n,可以先考虑将其拆分为两个数,1+(n-1),2+(n-2),...,一共有n//2种拆分方式,对于每一种拆分方式下的两个数,有可能它们当前的乘积已经最大,也有可能这两个数还能继续拆分,使乘积更大。dp数组dp[i]表示拆分i能得到的最大乘积,因此第一个for循环遍历n,第二个for循环从1开始遍历,模拟将i拆分为1+(i-1), 2+(i-2),...,(i-1)+1等等,在从左往右遍历的过程中,比i小的dp值已经计算得到,因此在当前拆分的基础上,可以比较继续拆分i-j会不会获得更大的乘积。dp数组从n=2开始初始化,第一个for循环就从3开始遍历,包含n;第二个for循环从1到i(不包含i),进一步优化中,数学上将一个数拆分为多个近似相同的数时乘积最大,两个数拆分到它们之间相同或只差1时,可以认为已经列举完可能出现最大值的拆分情况,因此j的遍历可以只到i//2。
动态规划问题中最难的部分是思路,代码实现倒是很简单:
class Solution:
def integerBreak(self, n: int) -> int:
dp = [0] * (n + 1)
dp[2] = 1
for i in range(3, n + 1):
for j in range(1, i):
dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j))
return dp[n]
LeetCode 96 不同的二叉搜索树
题目链接:96. 不同的二叉搜索树 - 力扣(Leetcode)
这道题也比较难想思路,需要对二叉搜索树有较为深刻的理解。
首先dp数组的含义是节点值从1到i互不相同的二叉搜索树的种数,和题目要求的从 1
到 n
互不相同的二叉搜索树种树是一致的。对于二叉搜索树,以不同的节点为根节点时,二叉树的构成是有固定限制的,比根节点小的值构成其左子树,大的值位于右子树,左右子树又能继续以根节点作为参考,构成不同的子树。左右子树的数值不同,但是数值的相对关系是类似的,比如1-3和2-4可以构成的二叉树种数是相同的,二叉搜索树的种数因此也就可以根据左右子树的节点数量获得各自的种数,左右的种数相乘得到以某个值为根节点时的二叉搜索树种数。
由于1到i中可以以i个节点作为根节点,不同根节点的二叉搜索树是不同的,因此计算dp[i]时,需要一层for循环累加以不同节点作为根时的结果:
class Solution:
def numTrees(self, n: int) -> int:
# dp数组含义:dp[i]为节点值从 1 到 i 互不相同的 二叉搜索树 的种数
dp = [0 for _ in range(n+1)] # dp[0]为空节点
dp[0], dp[1] = 1, 1
for i in range(2, n+1):
for j in range(i):
dp[i] += dp[j] * dp[i-j-1]
return dp[n]
小结
这两题的实现上还有一点点相似,都是两层for循环,时间复杂度是O(n^2),空间复杂度是O(n)。但dp数组的含义不同,第二层遍历的递推过程就有比较大的差异,一个是只取遍历过程中的最大值,一个需要不断累加。