动态规划在算法中的实践

本文借助LeetCode案例讲解动态规划在算法中的实践。以不含连续1的非负整数、不同的二叉搜索树为例,展示动态规划解题思路,对比暴力计算,其性能大幅提升。还分析了动态规划与分治策略、贪心算法的关系,强调合理应用算法思想的重要性。

【摘要】为了提高算法的效率,动态规划是在算法实践中经常使用的一个思想,有些问题会非常适合使用动态规划的思想来设计算法。本文将借助LeetCode上的一些例子,来讲解和说明动态规划在算法案例中的一些实践。

【关键词】 动态规划 LeetCode 算法效率 算法

一、引言

生活中会遇到很多的问题,在使用代码来进行抽象逻辑的时候,有时候会需要抽象成数学上的算法,来提高处理的效率。因为计算机的资源是有限的,并不能简单地、暴力地帮我们处理所有的问题。而且,很多重复的工作,也有必要通过抽象成算法来简化。比如举一个常见的例子,从1~n累加的和,就可以通过高斯算法来表示,得到(1+n)*n/2这样的结果,这样可以大大简化运算的步骤,从而提高算法的效率。同样的例子还有很多。算法可以有效的帮助到我们。

常用的有几大算法,分别是分治法、贪婪算法、动态规划、穷举法。当然还有一些分支定界法、回朔法、哈希表、深度优先搜索等等,在这里不做主要的介绍,在这里,主要介绍的是动态规划以及和动态规划思想有关算法的一些区别和需要注意的地方。

二、何为动态规划

动态规划(英文名称是:dynamic programming,这里的programing不是指程序代码,而是一种方法,一种算法思想)是解决某一类算法的思想,通过组合子问题的解,来求解最优解的方法。广泛应用在计算机科学、数学、生物信息学还有管理学科和经济学当中,通过把复杂的问题拆解成相对简单一些的子问题来求解。这样,通过分解成子问题,再通过子问题的最优集合,我们就可以方便的解决原来可能很复杂的问题。

关于子问题,有两个概念是需要了解的,第一个是最优子结构,动态规划要解决的,就是从一堆问题的解决方案中,寻找问题的最优解,当然这种最优解可能不存在,也可能用动态规划是无法寻找和找不到的。当找到最优解之后,通过把最优解拼装起来,就可以得到最后的解了。

如果问题比较复杂的话,那么非常有可能不止只有一个最优解,甚至,最优解也可以是一堆最优解的结合,这就是第二个关于子问题需要了解的,就是重复子问题。遇到这种情况,一般就需要通用递归重复子问题来得到最终的结果,当重复的子问题很多的时候,动态规划可以帮助我们减少很多重复的计算量。但是如果在使用递归方法来解决算法问题时,没有出现可以重复的子问题,那么就没必要非要使用动态规划的思想来解决,一般的递归方法也是可以很好地胜任的。

还有一个关于子问题的概念是,无后效性,也就是说每个子问题可以是相互独立的,在推导的过程中,后面的结果不该再去对这个子问题的结果产生影响。这一点也很重要,因为动态规划一般都是自顶向下来进行的,如果是子问题之间相互关联性很大的话,那就说明,这并不需要来通过动态规划来处理,也会严重影响到最后的算法的效率问题,或者这个算法有可能无法在有限的时间内终止,这都是不符合我们预期的。

我们可以这样说,寻找一个问题的子问题,并且完善子问题之间的关系的,写出状态转移方程,就是动态规划思想的基本思想过程了。也基本上可以说,如果能够证明,该问题存在最优的子问题,并且能够通过子问题的不断重复递归来得到最终的最优解,那么就说明该问题适合使用动态规划来解答。

以下通过几个案例,来详细解答这个过程。

三、案例一,不含连续1的非负整数

先来一道,关于数位动态规划的案例:

给定一个正整数n,找出小于或等于n的非负整数中,其二进制表示不包含连续的1的个数。1

方法1. 最简单粗暴的方法

首先我们用最简单粗暴的方法来计算一下这个案例,代码如下:

class Solution {
    public int findIntegers(int n) {
        int result = n + 1;
        for (int i = n; i > 0; i--) {
            int nextNumber = i;
            int remainder = nextNumber % 2;
            while (nextNumber != 0) {
                int lastRemainder = remainder;
                nextNumber = nextNumber / 2;
                remainder = nextNumber % 2;
                if (lastRemainder == 1 && remainder == 1) {
                    result--;
                    break;
                }
            }
        }
        return result;
    }
}

以上方法,当输入是2147483647时,得到的结果是3524578,计算耗时为30063ms。 十进制转二进制的方法就是除二取余法,以上方法,从0~n,每个数都计算一遍,只要有出现连续的1,就在总数上减一,这样就可以得到最终的结果。

方法2. 使用动态规划的思想来解析

上面的方法最后的耗时显然是一个非常长的时间,那么我们该如何使用动态规划来分析这道题呢?按照暴力计算的思路,就是把全部的数都计算一遍,但显然没有这个必要去全部都去计算一遍。 举个例子,比如数字7,7转成二进制的数是111
,转成二进制表示的话: 0 : 000,1 : 001,2 : 010,3 : 011,4 : 100,5 : 101,6 : 110,7 : 111

以上符合条件的数,分别是'0''1''10''100''101',一共5个数。我们可以把上面的数,使用二叉字典树的形式表达出来,如下图。

<
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值