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方程了,当一个问题不是那种很明显的动态规划的问题时,可以考虑升维去解题,即列二维状态方程。好了,今天的动态规划就聊到这里了,你学废了吗?

能看到这里的都是人才,你的点赞收藏是我最大的动力~ 也欢迎关注公众号「山主」,解锁更多干货~