代码随想录算法训练营Day41 | 343. 整数拆分 | 96. 不同的二叉搜索树

343. 整数拆分

题目链接 | 理论基础

  1. dp 数组下标的含义:dp[i] 是数字 i 进行整数拆分能得到的最大乘积
  2. 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]
  3. dp 数组的初始化:
    • 可以初始化 dp[0] = 0, dp[1] = 1,尽管这两种情况都不会被直接计算(所以不是必须的初始化),但是可以正确地得到 dp[2] = 1 的结果。
    • 也可以 dp[2] = 1 初始化后,开始循环
  4. 遍历顺序:根据递推,简单的从小到大,同时内部循环也要遍历所有的拆分 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 的左子节点。如下图,就能够得到当前问题与子问题的联系。

  1. dp 数组下标的含义:dp[i] 是 [1, i] 的数所能组成的独特 BST 的数量
  2. dp 递推公式:遍历所有可以拆分的 [0, j] 和 [j+1, i-1],加上所有的组合就能得到 dp[i]
    for j in range(i):
    	dp[i] += dp[i-1-j] * dp[j]
    
  3. dp 数组的初始化:dp[0] = 1
    • 如果一个节点都没有,就只能组成一个空的 BST
    • 这个初始化很重要,因为这决定了当 i 是 [1, i-1] 所组成的 BST 的最右叶子节点或是右侧父节点时仍然能够得到正确的答案
  4. 遍历顺序:根据递推,简单的从小到大,因为 i 就依赖于 [1, i-1] 的所有子问题的解
  5. 举例推导 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值