【力扣一刷】代码随想录day41(动态规划part3:343. 整数拆分、96.不同的二叉搜索树 )

【343. 整数拆分】中等题(需要仔细推理过程)

思路:动态规划

1、确定dp(i)的含义

dp(i)就是将 i 拆分为 k 个正整数的和( k >= 2 ),k个整数乘积的最大值。

2、确定递推关系

对于 n 而言,需要分 1~n/2 种情况讨论,用 j 代替其中一种

那么,j = 1时,剩余的和为n-1,只要比较 1 × dp(n-1) 和 1 × (n-1)哪个更大,即可得到将n拆分为正整数后一定包含正整数1的乘积最大值。以此类推,直至 j = n/2,遍历所有 j 的情况后统计乘积最大值,即可得到dp(n)。

3、确定初始值

根据题目的条件,2 <= n <= 58,则n的最小值为2,计算 n = 2 的时候需要遍历 j = 1 的情况,所以初始化 n=1 的 dp(1) = 1,便于计算 dp(2) 。

图解:

依照上面的推导,可以直接算出n=6的结果为9:

实现1  索引和真实值差1

注意:需要仔细检查索引是否正确

class Solution {
    public int integerBreak(int n) {
        int[] list = new int[n]; 
        list[0] = 1;  // 初始化n=1的最大值,便于计算 n=2 的情况
        for (int i = 1; i < n; i++){
            int max = 1;  // 最大值至少为1
            for (int j = 1; j <= (i + 1) / 2; j++){  // 列举 1 ~ (i+1)/2 的情况,并统计最大值
                int remain = i + 1 - j;
                int tmp = j * Math.max(remain, list[remain - 1]);// 最大值要么是 j * remain,要么是 j * dp(remain)
                max = Math.max(max, tmp); // 统计i+1所有情况的最大值
            }
            list[i] = max; // 记录最大值
        }
        return list[n-1];
    }
}

实现2  索引 = 真实值

好处:索引直接对应真实值,不需要考虑索引是否越界的问题

class Solution {
    public int integerBreak(int n) {
        int[] list = new int[n+1]; // 为了让索引和真实值对应,list[0]是多余的
        list[1] = 1;  // 初始化n=1的最大值,便于计算 n=2 的情况
        for (int i = 2; i <= n; i++){ // 直接统计到n,包含n
            int max = 1;  // 最大值至少为1
            for (int j = 1; j <= i / 2; j++){  // 列举 1 ~ i/2 的情况,并统计最大值
                int remain = i - j;
                int tmp = j * Math.max(remain, list[remain]);// 最大值要么是 j * remain,要么是 j * dp(remain)
                max = Math.max(max, tmp); // 统计i所有情况的最大值
            }
            list[i] = max; // 记录最大值
        }
        return list[n];
    }
}

简化版(理解后可以这么写)

class Solution {
    public int integerBreak(int n) {
        int[] list = new int[n+1];
        list[1] = 1;
        for (int i = 2; i <= n; i++){
            int max = 1;
            for (int j = 1; j <= i / 2; j++){
                max = Math.max(max, j * Math.max(i-j, list[i-j]));
            }
            list[i] = max;
        }
        return list[n];
    }
}
  • 时间复杂度:O(n²),两次for循环
  • 空间复杂度:O(n),额外的数组存储2-n的值

【96.不同的二叉搜索树】中等题(很难想到)

思路:动态规划

关键:只要节点数确定且元素不重复,则二叉搜索树种数确定(不需要考虑内部节点的值)

以 n=5,即共5个节点的二叉搜索树为例。

1、确定左右子树节点数的所有可能情况,统计各种情况下的二叉搜索树种数的和就是结果。

左子树节点数 - 右子树节点数 (除去根节点,5个节点就剩4个节点分配到左右子树中)

0 - 4

1 - 3

2 - 2

3 - 1

4 - 0

2、左子树或右子树节点数已知,则可以确定左子树/右子数对应的二叉搜索树种数,将左子树和右子数对应的二叉搜索树种数相乘,就是当前情况下所有的二叉搜索树种数。

例如:左子树节点数为1,右子数节点数为3,已知左子树对应的二叉搜索树种数为1,右子数对应的二叉搜索树种数为5,则当前情况下,所有二叉搜索树种树为 1*5=5 。

3、遍历所有的情况,统计所有情况二叉搜索树种数之和就得到结果。

已求的数组情况【0, 1, 2, 3, 4】->【1, 1, 2, 5, 14】

0 - 4 => 1*14=14

1 - 3 => 1*5=5

2 - 2 => 2*2=4

3 - 1 => 5*1=5

4 - 0 => 14*1=14

结果=14+5+4+5+14=42

class Solution {
    public int numTrees(int n) {
        int[] list = new int[n+1]; // 索引对应真实的节点数
        list[0] = 1; // 便于计算0个节点对应的二叉搜索树种数
        for (int i = 1; i <= n; i++){
            int sum = 0;
            // 除了根节点外,还剩 i-1 个节点,即左右子树的节点数之和为 i-1
            // 左/右子树节点数的最小值为0,最大值为i-1
            for (int j = 0; j <= i - 1; j++){ // j代表左子树的节点数,i-1-j代表右子树的节点数
                // 确定左右子树各种节点数时,根节点对应的二叉搜索树种数 = 左子树的二叉搜索树种数 * 右子树的二叉搜索树种数
                sum += (list[j] * list[i-1-j]);
            }
            list[i] = sum; // 记录i个节点对应的二叉搜索树数
        }
        return list[n];
    }
}

  • 时间复杂度:O(n²),两次for循环
  • 空间复杂度:O(n),额外的数组存储
  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值