动态规划是一种非常重要的算法思想,毕竟是一种思想,所以在逻辑层面的体现并没有一个固定的形式,但是处理问题的方式却都是本着一个原则:将一个问题递归分解为它的子问题进行求解,也许有人会问:这不是分治的思想嘛?因为问题分解的过程中往往会产生很多重复的子问题,这个时候动态规划和分支的区别就会体现出来,动态规划会将需要重复解决的子问题的结果在第一次计算之后就保存起来,之后碰到重复的子问题都可以直接得到结果,这样大大减少了递归过程中的算法时间复杂度。
动态规划能够解决问题的前提是该问题具有最优子结构的性质,即当问题的最优解包含了其子问题的最优解,那么这种问题就可以通过动态规划的思想求得最优解,这几篇有关动态规划的博客就拿leetcode上最简单的几道动态规划的题作为例子来讲。
leetcode 53
最大子序和
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
方法一:通过递归遍历所有的子序列和的情况,时间复杂度O(n^2)
一开始做这道题的时候,我想到的一个比较普通的方法,那就是通过递归求出所有子序列的和,通过遍历nums,每次都算出以nums[i]结尾的子序列之和,递归的过程中也有用到动态规划的思想,只是这种遍历所有子序列的方式的时间复杂度为O(n^2),给出代码:
class Solution {
public:
int maxSubArray(vector& nums) {
int last_sum[nums.size()];
memset(last_sum,0,sizeof(last_sum));
last_sum[0]=nums[0];
int max=last_sum[0];
for(int i=1;imax)
max=last_sum[j];
}
last_sum[i]=nums[i];
if(last_sum[i]>max)
max=last_sum[i];
}
return max;
}
};
int数组last_sum是用来保存每次计算以nums[i]结尾的所有子序列的和,一开始我使用的是一个n*n的二维数组,但是提交叫代码之后提示超出内存限制,后来考虑到每次迭代之后只需要保存最大的子序和就够了,没有必要把所有的子序和都保存下来,于是把n*n的二位数组改成一个n长度的一维数组。
但是既然题目都提示,用动态规划的思想是可以达到O(n)的时间复杂度的,那么肯定会有更加简单的方法。
方法二:动态规划递归求最大子序列和,时间复杂度O(n)
为了更好地理解这种方法,我们可以先理清一下思路:一个序列[a0,a1,a2,...,an-1,an]可以看作是它的子序列[a0,a1,a2,...,an-1]的基础上添加一个元素an得到的序列,如果每次我们都记录下来以ai为结尾的所有子序和中最大的那个,那么我们添加的这一个元素an给序列[a0,a1,a2,...,an-1]以an-1结尾的所有子序列和中最大的那个带来的影响就是:因为子序列求和要求其中的各个元素是连续的,因此只有两种可能:1.最大子序列和为以an-1结尾的最大子序列和加上an;2.最大子序列和为an本身。经过n次迭代,找出所有ai结尾的子序列的最大和,返回这个最大和即可。代码如下:
class Solution {
public:
int maxSubArray(vector& nums) {
vector::iterator it = nums.begin();
int maxSum = *it;
int theSum = *it;
for(it = it+1 ; it != nums.end(); it++){
theSum = max(theSum + *it, *it);
if(theSum > maxSum)
maxSum = theSum;
}
return maxSum;
}
};
第二种方法其实和第一种的意图非常相似,都是找出以ai结尾的所有子序列中的最大子序列之和,但是方法二更为简洁巧妙一些,时间复杂度也更小。
欢迎关注接下来几篇动态规划相关博客~