343. 整数拆分
- dp 数组下标的含义:
dp[i]
是数字 i 进行整数拆分能得到的最大乘积 - dp 递推公式:
dp[i] = max(dp[i], j * dp[i-j], j * (i-j))
,- 拆分当前 i 需要遍历
1 <= j < i
, 得到i = (i - j) + j
- 对于当前的
j
,可能得到的乘积是j * dp[i-j]
和j * (i-j)
,不断更新其中的最大值即可- 之所以要考虑这两种可能,是因为不知道
dp[i-j]
和(i-j)
的大小关系 - 之所以只考虑这两种可能,是因为在遍历
j
的过程中,会对j
进行拆分,无需在这里额外讨论dp[j]
- 之所以要考虑这两种可能,是因为不知道
- 拆分当前 i 需要遍历
- dp 数组的初始化:
- 可以初始化
dp[0] = 0, dp[1] = 1
,尽管这两种情况都不会被直接计算(所以不是必须的初始化),但是可以正确地得到dp[2] = 1
的结果。 - 也可以
dp[2] = 1
初始化后,开始循环
- 可以初始化
- 遍历顺序:根据递推,简单的从小到大,同时内部循环也要遍历所有的拆分
j
。- 要小心两个遍历的起止条件。
class Solution:
def integerBreak(self, n: int) -> int:
# dp[i] is the max product of sum given i
dp = [0] * (n+1)
# initialization, not necessarily, as
dp[0] = 0
dp[1] = 1
# dp formula
for i in range(1, n+1): # compute dp[i]
for j in range(1, i): # enumerate every possible combination of i
dp[i] = max(dp[i], j * dp[i - j], j * (i-j))
return dp[-1]
96. 不同的二叉搜索树
本题看上去跟 BST 的关系很大,但和 dp 似乎没什么关系,因为很难找到当前问题与子问题的关系。但实际上,题目给的例子已经很好地展示了这个关系。
最直观的想法是,对于 [1, i] 的数字组成的 BST,i+1 总是能成为这个 BST 的最右叶子节点和根节点的(右侧)父节点。但 n=3
的情况还有一种,3 是 1 的右子节点,2 是 3 的左子节点,这个似乎是个例外,所以之前的关系还需要再拓展。
由于 BST 的性质,对于 i+1 来说,可以从 [1, i] 中任取 [1, k] 组成 BST,然后将 i+1 作为这个 BST 的最右叶子节点,然后将 [k+1, i] 组成的 BST 作为 i+1 的左子节点。如下图,就能够得到当前问题与子问题的联系。
- dp 数组下标的含义:
dp[i]
是 [1, i] 的数所能组成的独特 BST 的数量 - dp 递推公式:遍历所有可以拆分的 [0, j] 和 [j+1, i-1],加上所有的组合就能得到
dp[i]
for j in range(i): dp[i] += dp[i-1-j] * dp[j]
- dp 数组的初始化:
dp[0] = 1
- 如果一个节点都没有,就只能组成一个空的 BST
- 这个初始化很重要,因为这决定了当 i 是 [1, i-1] 所组成的 BST 的最右叶子节点或是右侧父节点时仍然能够得到正确的答案
- 遍历顺序:根据递推,简单的从小到大,因为 i 就依赖于 [1, i-1] 的所有子问题的解
- 举例推导 dp 数组:
idx = [0, 1, 2, 3, 4, 5] dp = [1, 1, 2, 5, 14, 42]
class Solution:
def numTrees(self, n: int) -> int:
# dp[i] represents the number of unique BSTs using 1 to n
dp = [0] * (n+1)
# initialization, dp[0] is necessary (for null BST)
dp[0] = 1
dp[1] = 1
# dp formula
# for each BST using 1 to n-1, node with val=n can always be
# - the right child of some BST (using 1 to n-1)
# - the parent of some BST as left child (using 1 to n-1)
for i in range(2, n+1):
for j in range(i):
dp[i] += dp[j] * dp[i-j-1]
return dp[-1]
本题的难点在于找到 dp 的状态转移公式。关于当前问题与子问题的联系,还是通过观察例子得到的,的确很是复杂。
改进
for i in range(2, n+1):
for j in range((i-1) // 2 + 1):
dp[i] += dp[j] * dp[i-j-1] * 2
if i % 2:
dp[i] -= dp[(i-1) // 2] ** 2