前言
在面试或者笔试中,经常考到的知识点就是动态规划,他以各种形式出现在你面前,纵使你有公式套用,有的时候也做不出来,不知道问题的核心在哪,不知道子问题是什么,等等。为此,总结一下动归常使用的题型以及解题思路。
先看一个小故事加深一下印象:
A * "1+1+1+1+1+1+1+1 =?" *
A : "上面等式的值是多少"
B : 计算 "8!"
A *在上面等式的左边写上 "1+" *
A : "此时等式的值为多少"
B : quickly "9!"
A : "你怎么这么快就知道答案了"
A : "只要在8的基础上加1就行了"
A : "所以你不用重新计算因为你记住了第一个等式的值为8!动态规划算法也可以说是 '记住求过的解来节省时间'"
例题:输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大和为 6。
这个题如果用暴力解决肯定是通过不了的,像这种最优问题适合动态规划问题。
DP解题步骤:
- 1、找到问题的最优解。
本题也就是连续子数组的最大和
- 2、把大问题分解成小问题。
本题的子问题就是求以nums[i]为结尾的连续子数组的和,最终问题就是这些和的最大值。
- 3、状态转移方程
首先说明什么是状态,状态也就是动态规划过程中每执行一步所要存储得到的结果,本题的状态就是以nums[i]为结尾的连续子数组的和设为f(i),f(i)是怎么计算的呢?如果以nums[i-1]为结尾的连续子数组的和为负数(也就是上一个状态),那么f(i)肯定要舍弃它,直接取当前值nums[i]就好了;如果以nums[i-1]为结尾的连续子数组的和为正数,那么就保存下来,并且计算f(i-1)+nums[i]的值当做当前状态。所以状态转移方程为:f(i)=max(f(i-1)+nums[i],nums[i]).
- 4、考虑边界问题,也就是初始值、参数的取值
本题初始化取数组的第一个数为当前状态,然后从i=1开始遍历。
- 5、计算子问题的最优解,并且保存下来(数组),并且把结果当做后一步的前一个状态。
具体代码:
int maxSubArray(vector<int>& nums) {
//利用动态规划的思想
vector<int> dp(nums.size());
dp[0] = nums[0];
for(int i=1;i<nums.size();i++) {
dp[i] = max(nums[i],dp[i-1]+nums[i]);
}
return *max_element(dp.begin(),dp.end()); //注意这是求vector数组最值常用的方法,max_element返回的是一个地址。
}
本题有一个变形就是在一段数中找出两个连续子段,使其和最大。这个题思想不变,只需在增加一个dp数组保存从右到左的值就行了,然后相加就是结果。
动态规划写的比较好的博客:
blog.csdn.net/weixin_3827… blog.csdn.net/hollis_chua… blog.csdn.net/weixin_4335… blog.csdn.net/baidu_28312… blog.csdn.net/baidu_28312… blog.csdn.net/u013309870/…
动态规划的一些题目
1、丑数
2、最长不含重复字符的子字符串
3、礼物的最大价值(0-1背包问题)
4、爬楼梯(斐波那契数列)
5、数字翻译成字符串
6、打家劫舍
7、剪绳子
8、求最短路径问题
9、不同路径问题(求路径的数量)
「221. 最大正方形」、「1162. 地图分析」等。他们都以二维坐标作为状态,大多数都可以使用滚动数组进行优化。如果我们熟悉这类问题,可以一眼看出这是一个动态规划问题。
总结:
-
动态规划还有一定的优化空间,比如说二维dp状态数组可以压缩成一维,或者是在原来的数组上存储,一维dp状态数组可以用滑动数组(三个字轮流存储,之于三个值有关的,可以参考打家劫舍)等。
-
动态规划总是以小问题入手的,也就是自下向上的问题解决方法,如果是自上向下的方法就是递归+备忘录法。动态规划是对递归的一种优化,可以记录一些重复的子问题,大大提高了时间效率。
-
最重要的就是找到状态和状态转移方程,并且确定状态的所在区间以及边界条件。
-
「滚动数组思想」是一种常见的动态规划优化方法,在我们的题目中已经多次使用到,例如「剑指 Offer 46. 把数字翻译成字符串」、「70. 爬楼梯」等,当我们定义的状态在动态规划的转移方程中只和某几个状态相关的时候,就可以考虑这种优化方法,目的是给空间复杂度「降维」。
-
动态规划的题目分为两大类,一种是求最优解类,典型问题是背包问题,另一种就是计数类,比如这里的统计方案数的问题,它们都存在一定的递推性质。前者的递推性质还有一个名字,叫做 「最优子结构」 ——即当前问题的最优解取决于子问题的最优解,后者类似,当前问题的方案数取决于子问题的方案数。所以在遇到求方案数的问题时,我们可以往动态规划的方向考虑。通常如果我们察觉到了这两点要素,这个问题八成可以用动态规划来解决。读者可以多多练习,熟能生巧。