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

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

(上一章: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来说是极其重要的!

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
动态规划确定状态转移方程的关键是找到从上一个状态到下一个状态之间的变化和最终决策结果。在递归的过程中,我们可以将复杂的问题分解为最基本的小问题。对于每个小问题,我们需要找出可能达到最优的局部解,并放弃其他的局部解。这个过程称为动态规划。 在确定状态转移方程时,可以使用状态转移表来帮助理解整个过程。状态转移表可以记录不同状态之间的转移关系,从而找到准确的转移方程。要注意的是,状态转移方程动态规划算法中最关键的部分。它描述了问题的状态变化和决策结果的规律。 要确定状态转移方程,可以考虑以下几个步骤: 1. 定义状态:确定问题的状态是什么,可以是一个数值、一个数组或者其他形式的变量。 2. 定义状态转移方程:根据问题的特点,确定从上一个状态到下一个状态之间的变化和决策结果的表达式。 3. 建立状态转移表或递归函数:根据状态转移方程,建立状态转移表或递归函数来记录和计算问题的各个状态及其最优解。 4. 根据状态转移表或递归函数,找到问题的最优解。 需要注意的是,确定状态转移方程时需要考虑问题的特点和约束条件。有时候,可能需要通过试错和分析思考来找到合适的转移方程。关键是要理解问题的本质和目标,以及问题的局部最优解和全局最优解之间的关系。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [动态规划-如何推导出状态转移方程?](https://blog.csdn.net/somenzz/article/details/86027719)[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_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值