最大子数组和(动态规划+优化)

最大子数组和(动态规划+优化)_第1张图片

对于这种与什么什么子 有关的题,通常都可以使用动态规划来解,因为数组元素之间产生了联系

还是入门动态规划之三步走​​​​​​ 

第一步定义数组含义

这里我选择定义一个二维数组dp[i][j]为i与j分别代表nums数组下标,dp[i][j]则代表在nums下标为i到下标为j之间的元素和

第二步找数组元素之间的关系式,这里的关系式也比较容易找

面对一个连续的子数组,如果要求dp[ i ][ j ]的话,我们直接联想到dp[ i ][ j-1 ](不要忘了我们定义的数组含义),dp[ i ][ j ]与dp[ i ][ j-1 ]之间只相差一个 nums[ j ]

所以我们的关系式就是 dp[i][j] = dp[i][j - 1] + nums[j] 

第三步初始化

这里要注意的是数组越界问题,当j==0的时候如果代入关系式,就会产生数组越界问题

且当i==j 的时候,我们也要将数组元素赋值为nums[ j ],以便关系式正常使用

class Solution {
public:
int MAXN = -10001;
int maxSubArray(vector& nums) {
	int n = nums.size(),z=0;
	vector >dp(n);//定义二维数组dp[i][j]为i与j分别代表nums数组下标,dp[i][j]则代表在nums下标为i到下标为j之间的元素和
	for (int i = 0; i < n ; i++)
	{
		dp[i].resize(n);//每行开nums.size()+1个元素
	}

    while(z!=n)
    {
        dp[z][z]=nums[z];//初始化
        if(nums[z]>MAXN)//避免nums只有一个元素时输出-10001
        {
            MAXN=nums[z];
        }
        z++;
    }
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < n; j++)
		{
			if (j<=i || j - 1 < 0)//避免数组越界,和保证j>i
			{
				continue;
			}
			dp[i][j] = dp[i][j - 1] + nums[j];//关系式
			if (dp[i][j] > MAXN)
			{
				MAXN = dp[i][j];//找到所有子数组和中的最大值
			}
		}
	}
	return MAXN;
}
};

好了代码写完了,提交一下~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

超时了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

我发现该代码在遇到接近该题最大数据的时候即超时53. 最大子数组和 - 力扣(LeetCode) (leetcode-cn.com)

是不是我们写错了呢,其实不然,只是我们写的代码效率没有达到该题的要求

 以下题解来自LeetCode

我看了题解,发现人家的动态规划只使用了一维数组dp,看来动态数组第一步定义的不好也不行啊!!!

思路:

我们 不知道和最大的连续子数组一定会选哪一个数,那么我们可以求出 所有 经过输入数组的某一个数的连续子数组的最大和

因此我们的子问题就变成了求经过nums各个元素的连续子数组和

但是,很明显,我们发现貌似子问题之间的联系难以发现

例如:

经过 -3 的连续子数组的最大和是多少。

「经过 -3 的连续子数组」我们任意举出几个:

[-2,1,-3,4] ,-3是这个连续子数组的第 3 个元素;
[1,-3,4,-1] ,-3是这个连续子数组的第 2 个元素;

我们发现,我们不确定的是:-3 是连续子数组的第几个元素

因此我们改变子问题:求以nums各个元素结尾的连续子数组和

子问题 1:以 -2 结尾的连续子数组的最大和是多少;
以 -2 结尾的连续子数组是 [-2],因此最大和就是 -2。

子问题 2:以 1 结尾的连续子数组的最大和是多少;
以 1 结尾的连续子数组有 [-2,1] 和 [1] ,其中 [-2,1] 就是在「子问题 1」的后面加上 1 得到。−2+1=−1<1 ,因此「子问题 2」 的答案是 1。

到这里,我们不难发现子问题的联系了

大家发现了吗,如果编号为 i 的子问题的结果是负数或者 0,那么编号为 i + 1 的子问题就可以把编号为 i 的子问题的结果舍弃掉,这是因为:

  • 一个数 a 加上负数的结果比 a 更小;
  • 一个数 a 加上 0 的结果不会比 a 更大;

而子问题的定义必须以一个数结尾,因此如果子问题 i 的结果是负数或者 0,那么子问题 i + 1 的答案就是以 nums[i] 结尾的那个数

第二步找数组元素之间的关系式

最大子数组和(动态规划+优化)_第2张图片

代码实现如下:

class Solution {
public:
int MAXN = -10001;
int maxSubArray(vector& nums) {
    int n=nums.size();
    vectordp(n);//定义dp[i]为到下标为i的最大子数组和
    dp[0]=nums[0];//初始化
    MAXN=nums[0];//排除只有一个数字的情况
    for(int i=1;iMAXN)//找最大值
        {
            MAXN=dp[i];
        }
    }
    return MAXN;
    }
};

到这里就结束了吗???

不,结合我们得到的关系式,我们发现dp[ i ]只与dp[ i-1 ]有关

你可以想到什么???没错就是滚动数组

class Solution {
public:
    int maxSubArray(vector& nums) {
        int pre = 0, maxAns = nums[0];
        for (const auto &x: nums) {
            pre = max(pre + x, x);
            maxAns = max(maxAns, pre);//求最大值
        }
        return maxAns;
    }
};

下面分享另外一种做法(出自LeetCode)

该算法效率很高,不过较难想到吧~~

可以直接分析写出代码

  1.  假如全是负数,那就是找最大值即可,因为负数肯定越加越大。
  2.  如果有正数,则肯定从正数开始计算和,不然前面有负值,和肯定变小了,所以从正数开始。
  3.  当和小于零时,这个区间就告一段落了,然后从下一个正数重新开始计算(也就是又回到 2 了)。
class Solution {
public:
    int maxSubArray(vector& nums) {
        int ans = -1;//初始化
        int max = -INT_MAX;
        if(nums.size()==1)
            return nums[0];//nums只有一个元素的单独考虑
        for(int i = 0;imax)//更新最大值
                max= ans;
        }
        return max;

    }
};

你可能感兴趣的:((算法+例题)讲解,c++,动态规划)