算法学习打卡day40|343. 整数拆分、96.不同的二叉搜索树

343. 整数拆分

力扣题目链接
题目描述:
给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。
返回 你可以获得的最大乘积 。
示例 1
输入: n = 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
输入: n = 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

思路:

  • 思路就是把整数拆分成两数之和,三数以上可以不用管,因为三数以上其实就是两数拆分的,我们就用两个数来做题,分别取第一个数和第一个数的dp之间的最大值,以及第二个数和第二个数dp之间的最值,然后相乘就是当前组的最大值,最后不断循环和dp[i]比较取最值即可。
  • 动态规划五部曲:
    1. 分析dp数组含义:dp[ i ]数组代表下标为i的整数拆分后元素乘积的的最大值
    2. 分析递推公式: dp[i] = max(dp[i], max(dp[i - j], i - j) * max(dp[j], j));
    3. dp数组初始化:因为我们拆分是从1开始拆分的那么下标0可以不用管,把下标1赋值为1,2也赋值为1,因为2只能拆分为1+1.
    4. 遍历顺序:显然是从前往后遍历,我们遍历的时候从下标i = 3开始,内层循环是去拆分i,比如3从1开始拆分为1和2,但是这里上界其实到 i / 2就可以了,多了就重复了。
    5. 推导数组结果:在这里插入图片描述

代码实现:

int integerBreak(int n) {
        /*
        dp[n] = max(dp[1]dp[n - 1],dp[2]dp[n - 2]..., dp[n / 2]dp[n - n / 2]);
        即:dp[i] = max(dp[i], max(dp[i - j], i - j) * max(dp[j], j));*/
        vector<int>dp(n + 1, 0);
        dp[1] = 1, dp[2] = 1;
        int left = 0, right = 0;
        for (int i = 3; i <= n; ++i) {
            for (int j = 1; j <= i / 2; ++j) {
                left = max(j, dp[j]);
                right = max(i - j, dp[i - j]);
                dp[i] = max(left * right, dp[i]);
            }
            
        }
        return dp[n];
    }
  • 这里left取最值那一步是可以省略的,为什么?
    • 因为代码实现方法是对j和i- j同时判断是否需要拆分,但是就算 j 它再怎么拆也是拆分成1 - i之间的某个元素再加上另外一个数或一组数(省去left取最值写法其实把这一个数或一组数包到dp[i - j]里了),所以,取到最值的时候,j 一定是1- i之间的某个值,然后dp序列是递增的,那么最值一定不在后 i / 2 里(随着基数增大),所以 j 的上界可以是 i / 2。
    • 数学证明如下(节选自力扣题解的某位同学回答):
      • 因为j * dp[i - j]包含了dp[j] * dp[i - j],这是可以证明的: 对j最优拆分:j = a1 + a2 +…+an; 对i - j 最优拆分:i - j = b1 + b2 +…+bn; 所以有 dp[j] = a1 * a2 … an; dp[i - j] = b1 * b2 *… bn; dp[i] * dp[i - j] = (a1 *a2 *…an) * (b1 * b2 … bn) = a1 * (a2 * … * an * b1 * b2 … bn) 令 k = a1,必有i - k = a2 + … + an + b1 + b2 +…+ bn(这就是对 i - k 的一种拆分) 也就是说如果以上这种对i - k的一种拆分是最优的,那么必有dp[j] * dp[i - j] = k * dp[i - k] 所以此时j * dp[i - j]包含dp[j] * dp[i - j]; 如果以上这种对i - k的拆分不是最优的,那这种拆分方案虽不会被j * dp[i - j]包含但也不会是答案;

96.不同的二叉搜索树

力扣题目链接
题目描述:
给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

示例 1:
在这里插入图片描述
输入:n = 3
输出:5

思路:

  • 我的思路是这样的,1到n的每个数字都可以作为根节点,那么外层for循环还是老套度去求dp数组,内层for循环去决定哪个数子作为根节点,然后将左右节点的dp值乘起来,然后不断加到dp[i]上。
  • 动态规划五部曲:
    1. 分析dp数组含义:dp[ i ]数组代表下标为i的整数的二叉树种类
    2. 分析递推公式: dp[n] = dp[n - 1] * dp[0] + dp[n - 2] * dp[1] + ... + dp[1]*dp[n - 2] + dp[0]* dp[n - 1]
    3. dp数组初始化:因为我们左子树可以是0个节点,而且我们要做乘法,所以下标0赋值为1,把下标1也赋值为1,遍历的时候下标从2开始
    4. 遍历顺序:显然是从前往后遍历,我们遍历的时候从下标 i = 2开始,那么外层for循环还是老套度去求dp数组,内层for循环去决定哪个数子作为根节点,然后将左右子树节点数的dp值乘起来,然后不断加到dp[i]上。
    5. 推导数组结果:1 1 2 5 14…

代码实现

int numTrees(int n) {
        if (n < 2) return 1;
        //dp[n] = dp[n - 1] * dp[0] + dp[n - 2] * dp[1] + ... + dp[1]*dp[n - 2] + dp[0]* dp[n - 1]
        vector<int> dp(n + 1);
        dp[0] = 1, dp[1] = 1;
        for (int i = 2; i <= n; ++i) {
            for (int j = 1; j <= i; ++j) {
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }
        return dp[n];
    }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值