动态规划,它来了(一)
前言
大家好,我是bigsai,好久不见,甚是想念(天天想念)!
很久前就有小伙伴被动态规划所折磨,确实,很多题动态规划确实太难看出了了,甚至有的题看了题解理解起来都费劲半天。
动态规划的范围虽然确实是很广很难,但是从整个动态规划出现的频率来看,这几种基础的动态规划理解容易,学习起来压力不大,并且出现频率非常高。
这几个常见的动态规划有:连续子数组最大和,子数组的最大乘积,最长递增子序列(LIS),最长公共子序列(LCS),最长公共子串,最长公共子串,不同子序列。
什么是动态规划
首先很多人问,何为动态规划?动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。通俗一点动态规划就是从下往上(从前向后)阶梯型求解数值。
那么动态规划和递归有什么区别和联系?
总的来说动态规划从前向后,递归从后向前,两者策略不同,并且一般动态规划效率高于递归。
不过都要考虑初始状态,上下层数据之间的联系。很多时候用动态规划能解决的问题,用递归也能解决不过很多时候效率不高可能会用到记忆化搜索。
不太明白?
就拿求解斐波那契额数列来说,如果直接用递归不优化,那么复杂度太多会进行很多重复的计算。
但是利用记忆化你可以理解为一层缓存,将求过的值存下来下次再遇到就直接使用就可以了。
实现记忆化搜索求斐波那契代码为:
static long F(int n,long record[])
{
if(n==1||n==2) {return 1;}
if(record[n]>0)
return record[n];
else
record[n]=F(n-1,record)+F(n-2,record);
return record[n];
}
public static void main(String[] args) {
int n=6;
long[] record = new long[n+1];
System.out.println(F(n,record));
}
而动态规划的方式你可以从前往后逻辑处理,从第三个开始每个dp都是前两个dp之和。
public int fib(int n) {
int dp[]=new int[n+1];
dp[0]=0;
dp[1]=1;
for(int i=2;i<n+1;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
当然动态规划也能有很多空间优化,有些只用一次的值,你可以用一些变量去替代。有些二维数组很大也可以用一维数组交替替代。当然动态规划专题很大,有很多比如树形dp、状压dp、背包问题等等经常出现在竞赛中,能力有限这里就将一些出现笔试高频的动态规划!
连续子数组最大和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
dp的方法就是O(n)的方法。如果dp[i]表示以第i个结尾的最大序列和,而这个dp的状态方程为:
dp[0]=a[0]
dp[i]=max(dp[i-1]+a[i],a[i])
也不难解释,如果以前一个为截至的最大子序列和大于0,那么就连接本个元素,否则本个元素就自立门户。
实现代码为:
public int maxSubArray(int[] nums) {
int dp[]=new int[nums.length];
int max=nums[0];
dp[0]=nums[0];
for(int i=1;i<nums.length;i++)
{
dp[i]=Math.max(dp[i-1]+nums[i],nums[i]);
if(dp[i]>max)
max=dp[i];
}
return max;
}
ps:有小伙伴问那求可以不连续的数组最大和呢?你好好想想枚举一下正的收入囊中,那个问题没意义的。
连续子数组最大乘积
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
示例 :
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
连续子数组的最大乘积,这也是一道经典的动态规划问题,但是和普通动态规划又有点小不同。
如果数据中都是非负数,对于连续数组的最大乘积,那样处理起来和前面连续子数组最大和处理起来有些相似,要么和前面的叠乘,要么自立门户。
dp[0]=nums[0]
dp[i]=max(dp[i-1]*a[i],a[i])
但是这里面的数据会出现负数,乘以一个负数它可能从最大变成最小,并且还有负负得正就又可能变成最大了。
这时候该怎么考虑呢?
容易,我们开两个dp,一个dpmax[]记录乘积的最大值,一个dpmin[]记录乘积的最小值。然后每次都更新dpmax和dpmin不管当前值是正数还是负数.这样通过这两个数组就可以记录乘积的绝对值最大。
动态方程也很容易
dpmax[i]=max(dpmax[i-1]*nums[i],dpmin[i-1]*nums[i],nums[i])
dpmin[i]=min(dpmax[i-1]*nums[i],dpmin[i-1]*nums[i],nums[i])
看一个过程就能理解明白,dpmin就是起到中间过度的作用,记录一些可能的负极值以防备用。结果还是dpmax中的值。
文章转自公众号:bigsai