1.前言
今天聊一聊动态规划的问题,动态规划问题的一般形式就是求最值。在这类问题中,可能会有多个解,但是我们希望找到最优的解。
动态规划算法与前面的分治算法类似,基本思想也是将求解的问题分解为子问题求解。与分治算法不同的是,适合动态规划算法来求解的问题,分解得到的子问题太多,有的子问题还会被重复计算很多次。如果把计算过的子问题的结果保存,就能够避免大量重复的计算,这就是动态规划算法的基本思想。
解决动态规划问题的三步骤一般如下:
a.找子问题;
b.定义状态;
c.列状态(DP)方程;
本文将解决两个动态规划问题,来理解动态规划问题的解法,希望对你有帮助。
2.一维动态规划
我将动态规划问题按照难易程度分为一维和二维,先来看看一维吧。其实求解动态规划问题最重要的就是列出状态方程,也叫DP方程,我接触到的第一个动态规划问题就是「爬楼梯」问题,接下来就来看看「爬楼梯」问题~
这是一个简单的动态规划问题,首先来列出它的DP方程。因为每次可以爬1或2个台阶,所以到第n阶时,只能从n-1阶或n-2阶爬到n阶梯,假设到n阶有f(n)种方式,于是有这样的方程:f(n) = f(n-1) + f(n-2),当n=1或n=2时,f(1) = 1;f(2)=2,于是该题的状态方程如下:
根据状态方程写代码如下:
public int climbStairs(int n) {
if(n==1||n==2){
return n;
}
return climbStairs(n-1)+climbStairs(n-2);
}
其实这就是用分治的方法去求解的,很多重复的子问题都重复的去计算了,下面就来优化一下,前面也说到了把计算过的子问题保存起来,就能避免大量的重复计算。这里用一个数组来存储计算结果,由上面的DP方程去递推后面的结果。
public int climbStairs1(int n) {
if(n==1||n==2){
return n;
}
int[] arr = new int[n+1];
arr[0] = 1;
arr[1] = 2;
for (int i=3;i<arr.length;i++){
//递推
arr[i] = arr[i-1]+arr[i-2];
}
//返回结果
return arr[n-1];
}
通过用数组来存储,就大大的降低了计算的时间复杂度。
3.二维动态规划
接下来看看二维动态规划,所谓二维动态规划,就是二维数组的DP方程,直接进入主题,来看一个例题。
这个问题我用二维数组来解,主要想说明解决动态规划中的三要素:「子问题」「状态」「状态方程」。
首先找一下子问题:小偷偷或者不偷第n间房子;
再来定义一下状态:偷、不偷就是两个状态,可以分别用1、0表示;
最后列DP方程:
a.偷第i间房子时:第i-1间房子不能偷,偷的总金额最大值:Math.max(f(i,1) = f(i-1,0)+nums[i],f(i-1,0))
b.不偷第i间房子时:第i-1间房子必偷,偷的总金额最大值:f(i,0) = f(i-1,1)
接下来写代码:
public static int rob(int[] nums) {
if(nums.length==0){
return 0;
}
int[][] dp = new int[nums.length][2];
//定义第一件房子偷或者不偷
dp[0][1] = nums[0];
dp[0][0] = 0;
for(int i=1;i<nums.length;i++){
//第i间房子偷,最大总金额
dp[i][1] = Math.max(dp[i-1][0]+nums[i],dp[i-1][1]);
//第i间房子不偷,最大总金额
dp[i][0] = dp[i-1][1];
}
//返回最大值
return Math.max(dp[nums.length-1][1],dp[nums.length-1][0]);
}
这个问题在LeetCode上很多题解用到了滚动数组,但是这里用了二维数组去求解,主要是去理解怎么去定义问题的状态,然后列状态转移方程,这也是最重要的,然后一步一步优化,不然一上来就给个最简洁的写法,也是很懵逼的。
总结
动态规划三步骤:1.找子问题;2.定义状态;3.列DP方程。
最最最重要的就是列DP方程了,当一个问题不是那种很明显的动态规划的问题时,可以考虑升维去解题,即列二维状态方程。好了,今天的动态规划就聊到这里了,你学废了吗?
如果觉得文章对你有帮助的话,你的点赞收藏是我最大的动力!
也可以微信搜索「山主」,文章首发,解锁更多干货~