题目大意:
给定一个非空的整数数组nums[],要求求得它的一个子数组,使得该子数组所有成员的和是所有子数组中最大的。注意:子数组的所有成员在位置上必需是左右相邻的。
示例:
给出整数数组[-2,1,-3,4,-1,2,1,-5,4
],可求得它的子数组[4,-1,2,1
]的各位成员和是所有子数组中最大的,为6。
解题思路:
刷了近两个月的题,我发现自己对于动态规划这一难点仍然没有很好地掌握,或者说将这一思想更加灵活地运用到解决实际问题中,因此特地选择了一道dp(dynamic programming)的题目。可惜思考许久仍然不得其法。
本题的题面比较简单,也很有实际意义。思考时,首先想到的是暴力法,即从第一个成员开始,长度从1递增,计算各位成员之和并比较,保留其中的最大值。这种做法的复杂度高达O(N^2),毫无疑问在大规模用例时会超时。
基于这道题是一道动态规划的题目,我开始把目光往这方面转。动态规划的基本思想是很明了的:将一个大问题剪切成小的子问题,通过对这个小的子问题进行迭代求解得到大问题的解。问题的关键就在于如何对大问题进行切分,并进一步得到迭代的规律。由于本题的目标是求得给定整数数组的子数组,因此我想的是明确子数组的左右边界。可惜的是,由于有左、右边界两个变量纠缠其中,迭代规律很难总结。
在阅读了discuss中的最高票答案后,我才恍然大悟。高票解答中,它将“求解特定子数组”的问题转换成了“求解截止i(0<=i<=nums.size()) (必须包括nums[i])的子数组的最大和”。以示例中的例子进行说明,用数组dp[]表示子问题的解:
dp[0]=-2; //此时只有一个元素
dp[1]=1; //由于-2<1,因此截止位置1的子数组的最大和为1
dp[2]=-2; //首先需要说明的是,该子数组必须包含末位nums[i]=nums[2]=-3,至于为什么这样,后面会再作解释。由于相比dp[i-1],只增加了一位,因此只要判断前面一位的dp是大于0还是小于0,若大于0,则对于此处的dp是“有益的”,需将它包含进去,否则,只 计nums[i]即可。由于dp[1]=1>0,dp[2]=dp[1]+nums[2]=1+(-3)=-2
dp[3]=4; //由于dp[2]=-2<0,故dp[3]=nums[3]=4
dp[4]=3; //4-1
dp[5]=5; //3+2
dp[6]=6; //5+1
dp[7]=1; //6-5
dp[8]=5; //1+4
迭代公式为:dp[i]=(dp[i-1]>0?dp[i-1]:0)+nums[i];
显而易见,dp数组的最大值就是子数组和的最大值。回过头来也可以理解为什么一定要将nums[i]的值计算进去:因为题目要求子数组中所有成员位置上必须是左右相邻的,如果不将nums[i]的值强行包含进去,则可能出现最优解在后面但搜索不到的情形。
最后实现的代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n=nums.size();
int *p=new int[n],m; //数组p记录截止nums[i](必须包含nums[i])的子数组的最大和,m则是任一子数组的最大和
p[0]=nums[0];
m=nums[0];
for(int i=1;i<n;i++)
{
if(p[i-1]<=0)
p[i]=nums[i];
else
p[i]=p[i-1]+nums[i];
m=max(m,p[i]);
}
return m;
}
};
dp法的复杂度仅为O(N), 相较暴力法有了质的飞跃。但要想出这种解法,对于算法开发经验不足的人来说是很困难的。这既需要大量的实战经验积累,又需要对问题深入的分析和独到的见解。