目录
1、题目讲解
1. 1 题目描述如下:
1.2 题目分析:
给定一个数组,寻找数组元素之和最大的子串(包括数组自身),返回值是int类型的和。同样的变形还有,返回最大和对应的最大的子串,或者最大和对应的最小子串。
1.3 思路讲解:
本体一共给出三个思路求解,其中看到题目后第一时间想到的思路是
1.3.1 小学生加法
从做向右遍历数组,每当遇到正数的时候开始计算该正数和其后的一系列元素,如果该正数及其后面紧跟的若干元素之和小于0,则认为以该正数为首元素的子串(不包括该正数)没有贡献,(即最大和不会出现在以该正数为首元素的子串中)除了该正数本身。否则的话,在遍历数组的过程中找到最大的和并输出即可。
该思路存在以下问题:
1. 该方法肯定了正数对于求和的贡献,但是否认了负数的贡献。事实上,负数在求和的时候也是有贡献的,当数组中全是负数的时候,上述方法不再使用,因此还需要对数组中全是负数的情况做详细的讨论。
2. 该方法寻找的正数,并以正数为子串的首地址向后进行寻找。虽然符合要求的子串,必然是以正数为首元素的,但是在向后查找的过程中,很容易出现数组越界的情况,尤其是最后几个元素依然是正数的时候。(这时候,存在子串之和最大可能是最后一个元素或者最后几个元素的两种情况。因为要设置一个指针指向正数,一个指针要从该正数以后的元素查找。而且如果以该正数为首元素的子串和满足要求的时候,要将指向正数的指针移动到查找指针所指向的下一个位置,两个指针都可能存在越界的情况)
3.该方法不直观,很难保证一次手写完成后就通过所有的测试代码。即早期的代码不具有鲁棒性。对于以后的扩展也不是很方便。
具体代码如下:
class Solution1 {
public:
int maxSubArray(vector<int>& nums) {
int sum = 0;
//1、当数组中都是负数,返回最大的一个负数
if (*max_element(nums.begin(), nums.end()) < 0)
return *max_element(nums.begin(), nums.end());
//2、当数组中至少有一个是非负数
//找到一个非负数,向后查找,一直到该数后的子串之和小于零则结束一次查找,期间记录出最大的sum
unsigned i = 0,j = 0;
//int buf = 0;
//for(unsigned i = 0, j = 0; i < nums.size(), j < nums.size();) {
while (i < nums.size() - 1 && j < nums.size() - 1) {
if(nums[i] >= 0) {
sum = max(nums[i],sum);
j = i + 1;
int buf = nums[i] + nums[j]; // 用于保存局部子串之和
sum = max(sum, buf);
if(buf <= 0) {
i = j + 1;
continue;
}
while(buf > 0 && j < nums.size() - 1) {
//j++;
buf += nums[++j];
sum = max(sum, buf);
}
i = j;
// int count = 0;//记录指针移动的位置,方便输出最后的子串长度
// sum = max(sum,sum + buf);
// count++;
}
else i++;
}
sum = max(sum,nums[nums.size()-1]);
return sum;
}
};
代码中使用了max_element函数,该函数可以返回vector中最大的元素的位置。其在头文件<algorithm>中。代码执行的过程中,i,j需要反复的对应赋值,容易出错。
1.3.2 经典解法(小学奥赛思路)
思路一中的代码麻烦的主要原因是只考虑了正数的贡献,为了屏蔽掉负数累加对后面求和的影响,设置了一些列的条件来进行跳转。其实,屏蔽掉负数累加对后面求和的影响很简单,只需要将负数累加的结果设置为0即可。
思路1.3.1改进后的代码如下:
class Solution2 {
public:
int maxSubArray(vector<int>& nums) {
if(nums.empty()) return 0;
int maximum = nums[0];
unsigned right = 0;
int temp = 0;
while(right < nums.size()){
temp += nums[right];
maximum = max(maximum, temp);
if(temp < 0){
temp = 0;
}
++right;
}
return maximum;
}
};
代码的优点:
(1)不用考虑数组中是正数还是负数,只需要从数组的首元素开始进行赋值并比较即可。
(2) temp表示一个累加器,当累加器小于0的时候,就讲累加器清零,已屏蔽掉负数对后面求和的影响。
如果要输出最大和所应对的子串,再添加两个指针即可,一个指针指示最大和出现的子串的首地址(在temp < 0 的下一个位置处),另一个指针在更新sum的时候一起更新即可。两个指针之间的元素,就是对应的子串。
1.3.3 另设一个数组
leetcode中还提供了另一种做法,另外设置一个数组,其数组的大小和测试的数组大小一样,新设置的数组的元素存放的是测试数组当前位置及其以前的位置所能得到的最大的子串之和。
代码如下:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
//dp[i] : maxsubarray using nums[i]
// dp[i] = dp[i-1] > 0 ? nums[i] + dp[i-1] : nums[i]
int len = nums.size();
vector<int> dp(len, INT_MIN); // INT_MIN means int's min
cout << "INT_MIN = " << INT_MIN << endl;
dp[0] = nums[0];
for (int i = 1; i < len; i++) {
dp[i] = max(nums[i] + dp[i-1], nums[i]);
}
return *max_element(dp.begin(), dp.end());
}
};
代码的精髓在于新数组的构造,这种构造方式在很多地方都可以用的到。(具体例子是???)
要想输出对应的子串,那么应该输出新构建的数组中最大的元素(第一次出现的),与该最大元素对应位置往左最后一个非零元素之间长度。(即从最大的元素开始向左找)
2、分而治之法(未完)