在上一章中讲了基本的动态规划思路,但上一章中的状态转移(即小问题之间的关系)过于简单。

(上一章:https://blog.csdn.net/qq_42152365/article/details/107304816)

今天来看一道经典题:

动态规划,首先考虑状态是什么(“小问题”)以及状态之间的关系:

假设我们一共有6个数[1,2,3,4,5,6],现在已经写好了一个父节点4,手里还有几个数[1,2,3,5,6],根据二叉搜索树的定义,我要把[1,2,3]挂在左子树,[5,6]挂在右子树上。我们再考虑左子树,[1,2,3]均可以作为父节点,确定了一个父节点后,剩下的又是“确定父节点,且手里还有几个数”的问题。这样看来,状态应该是“确定父节点,手里还有数,有多少种组合方案”

但是这样一来,会出现一些问题,第一,我们的大问题是不确定父节点的,所以需要一个大循环。第二,更关键的是,我们难以存储“以某个父节点和剩下的若干个数为手头的数有多少种可能性”作为记录。(你可以试着按上面的思路写一段dp函数)

现在再来思考一下,“[1,2,3]挂在左子树,[5,6]挂在右子树上”这句话,事实上,[5,6]挂在子树上和[1,2]挂在子树上对于程序来说并没有任何区别,这就非常方便记录,而我们的大问题,恰好就是把[1,2,3...n]挂在一棵树上!那么我们是否有可能把状态确定为“把[1,2,3...n]挂在树上有几种可能”呢?

假设,这个状态为G(n)

显然:

(感谢LeetCode提供的公式图)

其中F(i,n)表示“以i为父节点,剩下数为手头的数有多少种可能性”,而F(i,n)中,1~(i-1)构成的子树数量就是G(i-1),(i+1)~n构成的子树数量就是G(n-i),仔细思考这个地方!这里就是状态转移的关键!

那么对于F(i,n)而言,总子树可能性,就应该等于左右子树数量的乘积:

就得到:

这就是本题的状态转移方程,是不是比上一章中的要复杂一些了。

有了这个状态转移方程,dp就非常清晰了。

当然,还有一个问题,dp的最小问题是啥?也就是说递归的出口在哪里?

显然,递归的出口在G(0)和G(1)也就是当我们手里没有数或者只有一个数的时候,就只能构成1个子树(注意,空树也算是1,不是0,如果算成0的话乘一下就全成0了!),当然,你也可以把G(2)、G(3)这些也算上。

class Solution {
public:int G_list[99];int dp(int n){if(n==0 || n==1){return 1;}if(G_list[n]!=0){return G_list[n];}for(int i=1;i<=n;i++){G_list[n]+=dp(i-1)*dp(n-i);}return G_list[n];}int numTrees(int n) {return dp(n);}
};

是不是感觉找对了转移方程就简单的一匹?

LeetCode官方提供的非递归解(算法是一样的):

    int numTrees(int n) {vector<int> G(n + 1, 0);G[0] = 1;G[1] = 1;for (int i = 2; i <= n; ++i) {for (int j = 1; j <= i; ++j) {G[i] += G[j - 1] * G[i - j];}}return G[n];}

G(n)在数学上被称为“Catalan”数列,其递推公式为:

所以更加优化的算法如下(状态是Cn,状态转移方程就是Catalan的递推公式):

    int numTrees(int n) {long long int res = 1;for(int i=0;i<n;i++){res = res*2*(2*i+1)/(i+2);}return res;}

可见,状态转移方程对dp来说是极其重要的!

【编程学习笔记】动态规划的核心——状态转移方程(递归方程)相关推荐

  1. 【学习笔记】C++ 核心编程(二)类和对象——封装

    内容来自小破站<黑马程序员C++>复习自用 [学习笔记]C++ 核心编程(二)类和对象--封装 4 类和对象 4.1 封装 4.1.1 封装的意义(一) 4.1.1 封装的意义(二) 4. ...

  2. 15Java网络编程学习笔记

    Java网络编程学习笔记 文章目录 1 网络基础 1.1 网络通信 1.2 网络 1.3 IP地址 1.5 域名 1.6 端口号 1.7 网络通信协议 1.8 TCP协议 1.9 UDP协议 2 In ...

  3. 多线程编程学习笔记——任务并行库(二)

    接上文 多线程编程学习笔记--任务并行库(一) 三.   组合任务 本示例是学习如何设置相互依赖的任务.我们学习如何创建一个任务的子任务,这个子任务必须在父任务执行结束之后,再执行. 1,示例代码如下 ...

  4. 多线程编程学习笔记——任务并行库(三)

    接上文 多线程编程学习笔记--任务并行库(一) 接上文 多线程编程学习笔记--任务并行库(二) 六.   实现取消选项 本示例学习如何实现基于Task的异步操作进行取消流程,以及在任务真正运行前如何知 ...

  5. WCF服务编程 学习笔记(1)

    你或许可以使用某一技术实现某些功能,可以按着指定的要求,完成特定的功能,实现某一想要的效果,这表示你可以使用该技术,会使用该技术,但是我们不能停留在使用的层次上,还要了解它们的运行机制,可能有点深了, ...

  6. Lua 编程学习笔记

    文章目录 Lua 编程学习笔记 一.环境安装 二.Lua 基本语法 1. 注释 2. 标识符 3. 变量 4. 数据类型 5. Lua 运算符 三.循环与流程控制 1. 循环 2. 流程控制 四.函数 ...

  7. shell脚本编程学习笔记2(xdl)——Bash变量

    shell脚本编程学习笔记2--Bash变量 1,变量简介 1,计算机内存单元2,设置规则字母数组下划线组成,不能以数字开头Bash中,默认类型字符串型,变量类型可修改 2,Bash变量规则 1,变量 ...

  8. SHELL编程学习笔记

    SHELL编程学习笔记 本文描述unix shell的各种应用实例,根据查阅资料和自我总结,作为自己今后复习的模板.本文搜集整理常用的shell应用实例,以例子详述unixshell部分命令的使用,着 ...

  9. 大数据第二阶段Python基础编程学习笔记(待完善)

    大数据第二阶段Python基础编程学习笔记(待完善) 第一章 Python基础语法 3.8 1-1Python概述 python基础部分: ●Python基础语法: 标识符,关键字,变量,判断循环.. ...

最新文章

  1. 谈谈常用清除浮动的方法
  2. 怎么通过controller层退出登录_控制层访问拦截
  3. java class 内容查看_015-JVM-使用javap查看class文件内容
  4. Python实现简易局域网视频聊天工具
  5. java项目开发实例java+ssh+mysql实现的共享自行车单车租赁|出租管理系统
  6. 企业要如何建立适合自己的PMF?
  7. python 计算离散点的微分和积分(超详细)
  8. 交换游戏 (记忆化搜索 状压)
  9. 最强损失函数分析:一般自适应鲁棒损失函数
  10. linux中nginx启动,重启,关闭命令
  11. linkbutton 的启用和禁用
  12. Windows10让资源管理器始终显示文件的详细信息
  13. 08 Cesium—影像服务-1
  14. MAC Python环境配置
  15. 关机程序C语言(快来整蛊你的小伙伴吧)
  16. ORB_SLAM2+realsense运行稠密建图
  17. mad和php的区别,独家揭秘评测恩雅eutx1尤克里里和卡卡mad尤克里里有啥区别?哪个好?深度剖析曝光...
  18. 输出“回”型数字方阵
  19. 小目标检测的基于高斯感受野的标签分配
  20. “东数西算”工程下如何建设新型算力网络

热门文章

  1. CSS动画——加载的菊花转动画
  2. 逻辑斯蒂回归公式推导
  3. Java中找不到符号是啥意思,什么会导致Java中找不到符号错误?
  4. MySql数据库基本练习题(带答案)
  5. android开发-RecyclerView上拉加载
  6. 艺术与审美见面课测验答案
  7. 4个消除眼袋的超省钱省时法
  8. linux程序员最好用的笔记本,程序员的最爱 微软Linux大会终于来了
  9. ubuntu安装备注
  10. 基于PSO算法的电网无功优化(附源码)