动态规划例题详解(三)

本文深入探讨了动态规划在解决整数拆分和构建不同二叉搜索树问题中的应用。通过五步法详细解析了动态规划数组的含义、动态转移方程、初始化和遍历顺序。对于整数拆分,重点在于求解乘积最大化的拆分方案;而在不同的二叉搜索树问题中,通过递归思想推导出动态转移方程,计算给定整数能构成的不同二叉搜索树的数量。
摘要由CSDN通过智能技术生成


前言

到目前为止,我们已经初步入门了动态规划,了解了两种题型:爬楼梯和不同路径,使用动态规划思维去解决问题:即有记忆的dp数组。也对五步法解决问题有了一定的理解,今天我们继续学习动态规划,趁热打铁!
今天学习的两种题型分别是整数拆分不同的二叉搜索树


整数拆分(进阶++)

我们先读题目:
leetcode343题
其实这道题目可以说是动态规划的高级应用了。首先读到题目,我们第一反应绝大多数都不会想到用动态规划解决这道题目,更像是用数论去解决。然而这题也可谓是动态规划的典型例题之一!

我们依旧使用五步法来先对这道题做一个剖析:
①了解动态数组dp[i]的含义及下标i的含义
设dp[i]为正整数i的乘积最大化结果,i为正整数i

②确定动态转移方程
因为dp[i]为正整数i的乘积最大化结果:共有两种方法得到dp[i]
·dp[i]=j×dp[i-j]: 与拆分出的数继续作拆分相乘(可以理解为类似递归的玩意)
·dp[i]=j×(i-j): 直接两数相乘
有心的小伙伴可以思考一下:这里的i,j分别是什么意思呢?

我理解的i是指计算到当前数,比如我要计算10,那么我肯定不会直接计算到10,而是通过2,3,4…一路拆分到10;自然,j就是拆分i的拆分数。
这时,细心的小伙伴很容易发现:题目中不是说将其拆分成至少两个正整数的和吗?为什么你不继续拆了?
因为j是从1开始遍历的,在此之前已经拆分计算过了,因此从1开始遍历j,比较所有拆分中得出最大的,写出以下动态转移公式

dp[i]=Math.max(dp[i],Math.max((i-j)×j, j×dp[i-j] ))

③确定动态数组dp[i]的初始化
写出动态转移方程后,最重要的莫过于初始化了,起点决定终点。
我们不妨考虑一下遍历顺序。

确定好遍历顺序后,我们初始化一下前面的结果
dp[2]=1
dp[3]=1*2=2
至于dp[0]=0,dp[1]=1,这个挺难解释,不过第一感觉就是这样的,有理解的小伙伴也可以教教我!

④确定遍历顺序
肯定是从小往大计算呀,给你一个2和一个58,你最先知道的是哪个,我赌是2【doge】,所以遍历顺序是从小到大。

⑤控制台模拟打印输出

这里给出本题的源代码:

class Solution {
    public int integerBreak(int n) {
        int[] dp=new int[n+1];
        //初始化
        dp[0]=0;
        dp[1]=1;

        dp[2]=1;
        if(n==2) return 1;
        dp[3]=2;
        
        for(int i=3;i<=n;i++){
            for(int j=1;j<=i;j++){
            //比较两数相乘与继续拆分的最大值,并且与当前最大值比较
                dp[i]=Math.max(dp[i],Math.max(dp[i-j]*j,(i-j)*j));
            }
        }
        return dp[n];
    }
}

由于本题笔者也不是很确定dp[i-j]与j乘积到底是如何演化的,因此我们通过控制台让过程透明化方便我们理解!
在这里插入图片描述

在这里插入图片描述

不同的二叉搜索树(进阶++)

我们依旧先看题目:
leetcode96题

首先,我们需要了解什么是二叉搜索树
二叉搜索树首先是一棵二叉树:结点最多能有两个“孩子”结点的树结构。
其次,二叉树有以下性质:

  • 每个节点中的值必须大于(或等于)存储在其左侧子树中的任何值。
  • 每个节点中的值必须小于(或等于)存储在其右子树中的任何值。

了解了它的基本概念后,我们就可以开始解题了!

因为二叉搜索树的构建都是可以以前一个树为基础结构构建下一棵二叉搜索树。我们参照Carl哥的图示[参考代码随想录]来认识理解这一过程:

在这里插入图片描述
在这里插入图片描述
这里我偷下懒,因为Carl哥已经解释的很清楚了,再次借用他的解释来阐述一个基本的情况。

来看看n为3的时候,有哪几种情况.
当1为头结点的时候,其右子树有两个节点,看这两个节点的布局,是不是和n为2的时候两棵树的布局是一样的啊!
(可能有同学问了,这布局不一样啊,节点数值都不一样.)(别忘了我们就是求不同树的数量,并不用把搜索树都列出来,所以不用关心其具体数值的差异)
当3为头结点的时候,其左子树有两个节点,看这两个节点的布局,是不是和n为2的时候两棵树的布局也是一样的啊!
当2位头结点的时候,其左右子树都只有一个节点,布局是不是和n为1的时候只有一棵树的布局也是一样的啊!
发现到这里,其实我们就找到的重叠子问题了,其实也就是发现可以通过DP[1]和DP[2]来推导出来DP[3]的某种方式.
思考到这里,这道题目就有眉目了.
DP[3]就是元素1为头结点搜索树的数量+元素2为头结点搜索树的数量+元素3为头结点搜索树的数量

元素1为头结点搜索树的数量=右子树有2个元素的搜索树数量左子树有0个元素的搜索树数量
元素2为头结点搜索树的数量=右子树有1个元素的搜索树数量
左子树有1个元素的搜索树数量
元素3为头结点搜索树的数量=右子树有0个元素的搜索树数量*左子树有2个元素的搜索树数量

可能到这里我们依旧很懵逼…
简单按照我的思路再捋顺一哈!

假设此时n=4了
那么无论以哪个节点为根节点,那么肯定会剩下3个节点作为孩子节点,而这三个孩子节点可以分布的顺序可能是(0,3)【左边0个节点,右边3个节点】(1,2)(2,1)(3,0)。
如果你理解了,很好!那么我们不妨想想如果上面的节点不存在,将这三个节点看成是以三个元素的某个元素为根节点,是不是重复上面步骤了呢?直到计算到最后面我们能轻易初始化出来的子节点,然后全部累加起来,就是当前节点的二叉搜索树可以构成的数目,并且以此可以寻找出下一个,哇,就是和动态规划思想一模一样嘛!

我们应用五步法再将思路补充完整:
了解动态规划数组的含义
dp[i]就是整数i可以构成的二叉搜索树的数量
确定动态转移方程
以当前元素为根结点的二叉搜索树:除该元素外还剩下i-1个元素,其中假设j-1个元素在左节点,那么还还剩i-j个元素在右

dp[i]+=dp[j-1]*dp[i-j]: //所有状态模拟一遍,从左i-1,右0到左0,右i-1

确定动态数组初始化
我们需要确定最小是可能为空节点,即假设左边没有节点,那么会存在dp[0]
当dp[0]=0时,无法计算后面的,所以默认dp[0]=1
dp[0]=1
dp[1]=1
dp[2]=2
确定遍历顺序
从上面的描述我们可知:二叉搜索树是从小到大的
⑤控制台打印输出

源代码如下:

class Solution {
    public int numTrees(int n) {
        int[] dp=new int[n+1];
        dp[0]=1;
        dp[1]=1;
        if(n==1) return 1;
        dp[2]=2;
        if(n==2) return 2;
        for(int i=3;i<=n;i++){
            for(int j=1;j<=i;j++){
                dp[i]+=dp[j-1]*dp[i-j];
            }
        }
        return dp[n];
    }
}

总结

现在是晚上12点,由于我贪玩打了一晚上游戏到睡觉前才想起来还有结尾没有结呜呜呜,硬是肝了,也希望读者能有良好作息,晚安。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Astronaut_001

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值