41. 最大子数组
给定一个整数数组,找到一个具有最大和的子数组,返回其最大和。
样例1:
给出数组[−2,2,−3,4,−1,2,1,−5,3]
,符合要求的子数组为[4,−1,2,1]
,其最大和为6
样例2:
给出数组[1,2,3,4]
,符合要求的子数组为[1,2,3,4]
,其最大和为10
挑战
要求时间复杂度为O(n)
注意事项
子数组最少包含一个数
方法一:
class Solution {
public:
int maxSubArray(vector<int> &nums) {
// write your code here
int size = nums.size();
int max = nums[0];
int nowM = 0;
for (int i = 0; i < size; i++) {
nowM += nums[i];
if (nowM > max) {
max = nowM;
}
if (nowM < 0) {
nowM = 0;
}
}
return max;
}
};
方法二:
class Solution {
public:
int maxSubArray(vector<int> nums) {
//sum记录前i个数的和,maxSum记录全局最大值,minSum记录前i个数中0-k的最小值
int sum = 0, minSum = 0, maxSum = INT_MIN;
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
maxSum = max(maxSum, sum - minSum);
minSum = min(minSum, sum);
}
return maxSum;
}
};
42. 最大子数组 II
给定一个整数数组,找出两个 不重叠 子数组使得它们的和最大。每个子数组的数字在数组中的位置应该是连续的。返回最大的和。
例1:
输入: [1, 3, -1, 2, -1, 2] 输出: 7 解释: 最大的子数组为 [1, 3] 和 [2, -1, 2] 或者 [1, 3, -1, 2] 和 [2].
例2:
输入: [5,4] 输出: 9 解释: 最大的子数组为 [5] 和 [4].
挑战
要求时间复杂度为 O(n)
注意事项
子数组最少包含一个数
思路:从左自右、从右自左分别遍历数组。每次遍历均记录当前最大的单子数组,用2个数组left,right保存。如left[i]的值表示nums从0至i中最大子数组的值,right[i]的值表示nums从i至size-1中最大子数组的值。最后遍历left,right数组,left[i]+right[i+1]表示在第i位拆分数组,得到其子数组的和。
class Solution {
public:
int maxTwoSubArrays(vector<int> &nums) {
// write your code here
int size = nums.size(), i = 0, sum = 0, maxValue = nums[0];
int *left = new int[size];
left[0] = nums[0];
for(i=0; i<size; i++)
{
sum += nums[i];
if(sum > maxValue)
{
maxValue = sum;
}
if(sum < 0)
{
sum =0;
}
left[i] = maxValue;
}
int *right = new int[size];
sum = 0;
maxValue = right[size-1] = nums[size-1];
for(i=size-1; i>=0; i--)
{
sum += nums[i];
if(sum > maxValue) {
maxValue = sum;
}
if(sum < 0) {
sum = 0;
}
right[i] = maxValue;
}
int result = 0x80000000;
for(i=0; i<size-1; i++) {
result = (result > left[i]+right[i+1])?result:(left[i]+right[i+1]); //left[i]+right[i+1]表示在第i位拆分数组,得到其子数组的和
}
delete[] left;
delete[] right;
return result;
}
};
我们来看看0x80000000的输出
0x80000000 的二进制位
原码 1000 0000 0000 0000 0000 0000 0000 0000
若最高位为符号位,则为-0,可是输出int i = 0x80000000 发现i= -(2^31)
原因是在十六进制中负数的二进制原码的最高位是符号位,后面的31位为序号位,不是值位。1后面的000 0000 0000 0000 0000 0000 0000 0000,表示序号1,表示负数中,从小到大的第一位。
由于int的最小值为-2^31,排在负数从小到大的序号1,所以int i = 0x80000000 输出为 -(2^31)
43. 最大子数组 III
给定一个整数数组和一个整数 k,找出 k 个不重叠子数组使得它们的和最大。每个子数组的数字在数组中的位置应该是连续的。返回最大的和。
样例1
输入: List = [1,2,3] k = 1 输出: 6 说明: 1 + 2 + 3 = 6
样例2
输入: List = [-1,4,-2,3,-2,3] k = 2 输出: 8 说明: 4 + (3 + -2 + 3) = 8
注意事项
子数组最少包含一个数
思路:
local[i][j]的值表示前j个数字分为i个子数组的最大和,其中确定第j个数包含在第i个子数组中
global[i][j]的值表示前j个数字分为i个子数组的最大和,其中第j个数未必包含在第i个子数组中
当第j个数到来时,local[i][j]的可能情况为:
1.第j个数单独成为一个子数组,即第i个子数组,意味着之前的j-1个数已经组成了i-1个子数组,且第j-1个数未必是包含在第i-1个子数组中的(或者说我们并不关心它有没有包含)。因此这种情况的表达式为global[i-1][j-1]+nums[j-1]
2.第j个数并入之前的j-1个数中,至少与第j-1个数(或更多前面的数)组成i个子数组,这里的情况便是第j-1个数被包含在第i个子数组内,然后与最新到来的第j个数共同组成第i个子数组。因此这种情况的表达式为local[i][j-1]+nums[j-1]
local[i][j]=max(global[i-1][j-1],local[i][j-1])+nums[j-1];
然后考虑global[i][j]的可能情况:
1.若第j个数到来之后确定被包含在第i个子数组中,即成为了local[i][j]
2.若第j个数到来后可能未被包含在第i个子数组中,此时第j个数对全局最优不一定贡献,因此还是沿用第j-1个数时的全局最优值,通过比较大小来判断该数有没有贡献
global[i][j]=max(local[i][j],global[i][j-1]);
最后考虑如何填表。该表的行代表第i个子数组,列代表第j个数。很显然i<=j,即只需要填右上半部分表。
由于local的计算会用到global的左上角以及自身的左侧,global的计算会用到自身的左侧以及local的当前位置。因此迭代过程中先填local再填global。
应该只需要保证global[0][0]处为0即可进行填表。但由于数可能为负数,比较大小时不能直接跟0比,因此global和local左侧边界应该为负的极小值。
class Solution {
public:
int maxSubArray(vector<int> &nums, int k) {
// write your code here
int n = nums.size();
vector<vector<int> > local(k + 1, vector<int>(n + 1));
vector<vector<int> > global(k + 1, vector<int>(n + 1));
for (int i = 1; i <= k; i++)
{
local[i][i] = local[i - 1][i - 1] + nums[i - 1];
global[i][i] = local[i][i];
for (int j = i + 1; j <= n; j++)
{
local[i][j]=max(global[i-1][j-1],local[i][j-1])+nums[j-1];
global[i][j]=max(local[i][j],global[i][j-1]);
}
}
int result = INT_MIN;
for (int i = k; i <= n; i++)
{
result = max(result, local[k][i]);
}
return result;
}
};
45. 最大子数组差
给定一个整数数组,找出两个不重叠的子数组A和B,使两个子数组和的差的绝对值|SUM(A) - SUM(B)|最大。
返回这个最大的差值。
例1:
输入:[1, 2, -3, 1] 输出:6 解释: 子数组是 [1,2] 和[-3].所以答案是 6.
例2:
输入:[0,-1] 输出:1 解释: 子数组是 [0] 和 [-1].所以答案是 1.
挑战
时间复杂度为O(n),空间复杂度为O(n)
注意事项
子数组最少包含一个数
思路:维护四个数组,当前位置左边的最大子数组和,最小子数组和。当前位置右边的最大子数组和,最小子数组和。
然后枚举分割线,扫描一下即可。
class Solution {
public:
/**
* @param nums: A list of integers
* @return: An integer indicate the value of maximum difference between two substrings
*/
int maxDiffSubArrays(vector<int> &nums) {
// write your code here
int size = nums.size(), i = 0, sum = 0, maxValue = nums[0],minValue=nums[0];
int *left_max = new int[size];
for(i=0; i<size; i++)
{
sum += nums[i];
if(sum > maxValue)
{
maxValue = sum;
}
if(sum < 0)
{
sum =0;
}
left_max[i] = maxValue;
}
int *right_max = new int[size];
sum = 0;
maxValue= nums[size-1];
for(i=size-1; i>=0; i--)
{
sum += nums[i];
if(sum > maxValue) {
maxValue = sum;
}
if(sum < 0) {
sum = 0;
}
right_max[i] = maxValue;
}
int *left_min=new int[size];
sum=0;
for(i=0; i<size; i++)
{
sum += nums[i];
if(sum < minValue)
{
minValue = sum;
}
if(sum > 0)
{
sum =0;
}
left_min[i] = minValue;
}
int *right_min=new int[size];
sum=0;
minValue=nums[size-1];
for(i=size-1;i>=0;i--)
{
sum += nums[i];
if(sum < minValue)
{
minValue = sum;
}
if(sum > 0)
{
sum =0;
}
right_min[i]=minValue;
}
int result = 0;
for(i=0; i<size-1; i++) {
result = max(result,abs(left_max[i]-right_min[i+1]));
result=max(result,abs(left_min[i]-right_max[i+1]));
}
delete[] left_max;
delete[] left_min;
delete[] right_max;
delete[] right_min;
return result;
}
};