最大连续子数组和与积
和
问题描述:给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
暴力求解法
对于一个整数数组,他所有的连续子树组的个数可以通过归纳求得:
以第1个元素为首的连续子数组有n个;
以第2个元素为首的连续子数组有n-1个;
……
以第n个元素为首的连续子数组有1个;
因此总共有: s u m = ∑ i = 1 n i = n ( n + 1 ) / 2 sum = \sum_{i=1}^ni=n(n+1)/2 sum=∑i=1ni=n(n+1)/2。
只需要比较所有的子树组的和的大小即可。
C++实现:
int MaxSum(vector<int> &nums){
int max = INT_MIN;
int sum = 0;
for(int i = 0;i < nums.size();++i){
for(int j = i;j < nums.size();++j){
//求和
sum = 0;
for(int k = i;k <= j;++k){
sum+=nums[k];
}
max = max(max,sum);
}
}
return max;
}
时间复杂度分析: (计算加法次数)
有
n
个
大
小
为
1
的
子
数
组
有
n
−
1
个
大
小
为
2
的
子
数
组
有
n
−
2
个
大
小
为
3
的
子
数
组
…
…
有
1
个
大
小
为
n
的
子
数
组
因
此
加
法
次
数
:
s
u
m
=
(
n
−
1
)
∗
2
+
(
n
−
2
)
∗
3
+
…
…
+
1
∗
n
=
∑
i
=
1
n
−
1
i
(
n
−
i
+
1
)
=
∑
i
=
1
n
−
1
(
i
(
n
+
1
)
−
i
2
)
=
(
n
+
1
)
∑
i
=
1
n
−
1
i
−
∑
i
=
1
n
−
1
i
2
=
n
(
n
−
1
)
(
n
+
1
)
/
2
−
n
(
n
−
1
)
(
2
n
−
1
)
/
6
=
n
(
n
−
1
)
6
(
3
(
n
+
1
)
−
2
n
+
1
)
=
n
(
n
−
1
)
(
n
+
4
)
/
6
有n个大小为1的子数组\\ 有n-1个大小为2的子数组\\ 有n-2个大小为3的子数组\\ ……\\ 有1个大小为n的子数组\\ 因此加法次数:\\ sum=(n-1)*2+(n-2)*3+……+1*n=\sum_{i=1}^{n-1}i(n-i+1)\\ =\sum_{i=1}^{n-1}(i(n+1)-i^2) =(n+1)\sum_{i=1}^{n-1}i-\sum_{i=1}^{n-1}i^2 =n(n-1)(n+1)/2-n(n-1)(2n-1)/6\\ =\frac{n(n-1)}6(3(n+1)-2n+1)=n(n-1)(n+4)/6
有n个大小为1的子数组有n−1个大小为2的子数组有n−2个大小为3的子数组……有1个大小为n的子数组因此加法次数:sum=(n−1)∗2+(n−2)∗3+……+1∗n=i=1∑n−1i(n−i+1)=i=1∑n−1(i(n+1)−i2)=(n+1)i=1∑n−1i−i=1∑n−1i2=n(n−1)(n+1)/2−n(n−1)(2n−1)/6=6n(n−1)(3(n+1)−2n+1)=n(n−1)(n+4)/6
综上可知,时间复杂度为
O
(
n
3
)
O(n^3)
O(n3)
分治算法
采用分治算法的一般思路:
- 分割: 把原问题分割为若干规模更小的子问题,每个子问题与原问题类型相同;
- 递归求解: 递归求出子问题的解。在求解子问题时,如果子问题规模较大不易求解,则将子问题继续分割直到容易求解为止。
- 合并: 将子问题的解合并得到原问题的解
在本题中:
-
分割,每次使用对半分,在分割出的数组中寻找它们最大的和。
-
递归求解。
-
合并。除了分割的数组可能包含最大和。还有一种可能那就是,当前数组的最大和跨越了两个分割的数组,因此需要计算出跨越这两个数组的最大和。最后利用递归求解的两个分割的最大和,以及跨越分割的最大和求出当前数组下的最大连续子序和。
C++实现:
int MaxSum1(vector<int> &nums,int left,int right){
//终止条件
if(left==right) return nums[left];
int mid = (left+right)/2;
//分割,并递归求解
int sumLeft = MaxSum1(nums,left,mid);
int sumRight = MaxSum1(nums,mid+1,right);
//合并
int sum1 = INT_MIN,sum2 = INT_MIN,tmp = 0;
for(int i=mid+1;i<=right;++i){
tmp+=nums[i];
sum1=max(sum1,tmp);
}
tmp=0;
for(int i=mid;i>=left;--i){
tmp+=nums[i];
sum2=max(sum2,tmp);
}
sum1 = sum1+sum2;
sum2 = max(sumLeft,sumRight);
sum2 = max(sum1,sum2);
return sum2;
}
时间复杂度分析:
合并解的过程,有
n
n
n次加法。则有递归式:
T
(
n
)
=
2
T
(
n
/
2
)
+
n
T(n)=2T(n/2)+n
T(n)=2T(n/2)+n
利用主方法可得;
T
(
n
)
=
Θ
(
n
lg
n
)
T(n)=\Theta(n\lg n)
T(n)=Θ(nlgn)
动态规划
设 S [ i ] S[i] S[i]为 A [ i ] A[i] A[i]为末尾的最大和。则有 S [ i ] = m a x ( A [ i ] , S [ i − 1 ] + A [ i ] ) S[i]=max(A[i],S[i-1]+A[i]) S[i]=max(A[i],S[i−1]+A[i])。则可计算出所有以 A [ i ] A[i] A[i]为末尾的最大和,然后比较所有以 A [ i ] A[i] A[i]为末尾的最大和,得其最大者。
C++实现:
int MaxSum1(vector<int> &nums,int left,int right){
vector<int> S(nums.size(),0);
S[0] = nums[0];
int M = S[0];
for(int i = 1;i<nums.size();++i){
S[i]=max(S[i-1]+nums[i],nums[i]);
M = max(M,S[i]);
}
return M;
}
时间复杂度分析:
易得复杂度为: Θ ( n ) \Theta(n) Θ(n)
积
问题描述:将和题目中的和改为积即可。
暴力求解法与求和类似,略。
分治算法
这与求和不同在于如何合并解。求和合并解的只需要将分割后的两个序列的最大和(一个最大和是从右边开始,一个是从左边开始)相加就行了,但是乘积却不同。因为有负数的存在,因此跨越的最大积在分割后的两个序列的最大积的积与最小积之间取得。
有C++代码:
int MaxProduct1(vector<int> &nums,int left,int right){
//终止条件
if(left==right) return nums[left];
int mid = (left+right)/2;
//分割,并递归求解
int productLeft = MaxProduct1(nums,left,mid);
int productRight = MaxProduct1(nums,mid+1,right);
//合并
int Product1 = INT_MIN,Product2 = INT_MIN;
int Product3 = INT_MAX,Product4 = INT_MAX;
int tmp = 1;
for(int i=mid+1;i<=right;++i){
tmp*=nums[i];
Product1=max(Product1,tmp);
Product3=min(Product3,tmp);
}
tmp = 1;
for(int i=mid;i>=left;--i){
tmp*=nums[i];
Product2=max(Product2,tmp);
Product4=min(Product4,tmp);
}
//最大与最小
Product1 = Product1*Product2;
//cout<<Product1<<endl;
Product3 = Product3*Product4;
//cout<<Product3<<endl;
Product1 = max(Product1,Product3);
Product3 = max(productLeft,productRight);
Product1 = max(Product1,Product3);
return Product1;
}
动态规划
同样地,与最大和类似。设
P
[
i
]
P[i]
P[i]为
A
[
i
]
A[i]
A[i]为末尾的最大积,设
M
[
i
]
M[i]
M[i]为
A
[
i
]
A[i]
A[i]为末尾的最小积,,与最大和不同,由于存在负数。则有
S
[
i
]
=
m
a
x
(
A
[
i
]
,
S
[
i
−
1
]
∗
A
[
i
]
,
M
[
i
−
1
]
∗
A
[
i
]
)
M
[
i
]
=
m
i
n
(
A
[
i
]
,
M
[
i
−
1
]
∗
A
[
i
]
,
S
[
i
−
1
]
∗
A
[
i
]
)
S[i]=max(A[i],S[i-1]*A[i],M[i-1]*A[i])\\ M[i]=min(A[i],M[i-1]*A[i],S[i-1]*A[i])
S[i]=max(A[i],S[i−1]∗A[i],M[i−1]∗A[i])M[i]=min(A[i],M[i−1]∗A[i],S[i−1]∗A[i])
int MaxProduct2(vector<int>& nums){
vector<int> S(nums), M(nums);
for (int i = 1; i < nums.size(); ++i) {
S[i] = max(S[i - 1] * nums[i], max(nums[i], M[i - 1] * nums[i]));
M[i] = min(M[i - 1] * nums[i], min(nums[i], S[i - 1] * nums[i]));
}
return *max_element(S.begin(), S.end());
}
Reference
[1] https://leetcode-cn.com/problems/maximum-subarray/
[2]https://leetcode-cn.com/problems/maximum-product-subarray/
[3]算法竞赛入门经典(第2版),刘汝佳,8.1