你好,我是goldsunC
让我们一起进步吧!
最大子序列和
Question
:给定整数
A
1
,
A
2
,
.
.
.
A
N
A_{1},A_{2},...A_{N}
A1,A2,...AN(可能有负数),求
∑
k
=
i
j
A
k
\sum_{k=i}^{j}A_{k}
∑k=ijAk的最大值(为方便起见,如果所有整数均为负数,则最大子序列和为0)。
示例:
IN : [-2,11,-4,13,-5,-2]
OUT : 20 即从A2-A4
思路分析
暴力求解
第一种思路很简单,暴力求解。从数组第一个整数开始遍历每一种长度的最大值,然后遍历之后的最大值即是最大子序列和,如果最大值为负数则初始化为0,例如:
指定数组为:[-2,11,-4,13,-5,-2]
首先初始化最大值为-2,因为-2为负数所以最大值为0,然后计算-2+11,然后更新最大值为9,然后计算-2+11-4,保持当前最大值,然后计算-2+11-4+13,更新最大值为18,一直加到数组最后一位数,再从11开始求最大值,然后再从-4开始,就这样把数组每一种子序列和都遍历了,求得最大值。
源码(Java):
public class maxValueSum {
public static void main(String[] args) {
int[] array = {-2,11,-4,13,-5,-2};
System.out.println(Max(array));
}
public static int Max(int[] array) {
int maxnum = array[0];
for (int i=0;i<array.length;i++) {
int currentnum = 0;
for (int j=i;j<array.length;j++) {
currentnum += array[j];
if (currentnum>maxnum)
maxnum = currentnum;
}
}
return maxnum;
}
}
源码(Python):
def Max(array) :
maxnum = 0
for i in range(len(array)):
num = 0
for j in range(i,len(array)):
num += array[j]
if num > maxnum:
maxnum = num
return maxnum
array = [-2,11,-4,13,-5,-2]
print(Max(array))
上面暴力求解法很简单,但是有个突出的问题,就是时间复杂度为O( N 2 N^{2} N2),当涉及到大量输入的时候,效率非常低。
分治+递归求解
通常来讲,我们遇见递归的时候往往伴随的还有很高的时间复杂度,但对于这个问题来讲还有一个使用递归
+分治
实现的稍微复杂的方法,其时间复杂度却仅为O(
N
log
N
N\log{N}
NlogN),或许这个算法就是体现递归为例的最好范例了。
这个算法使用一种"分治"
的策略,其想法是把问题分成两个大致相等的子问题,然后采用递归对他们求解,这是分
,治
是将两个子问题的解合并到一起并再做些少量的附加工作,最后得到整个问题的解。
假如将传入的数组从中间分成左右两个部分,例如:
[-2,11,-4,13,-5,-2]
->[-2,11,-4]
和[13,-5,-2]
那么问题的解可能出现在三处出现:
- 左半部分
[-2,-11,-4]
- 又半部分
[13,-5,-2]
- 或者横跨两个部分,它一定包括左半部分的最后一个值和右半部分的第一个值。
对上述问题求解得:
左半部分得最大值为11
,右半部分的最大值为13
,而横跨这两个部分包含左半部分最后一个值以及右半部分第一个值的最大值为20
,即11-4+13,将三者对比,则返回最大值20。
而这个时候我们求各部分最大值是直接看出来的,那实际应该怎么求呢?机器可看不出来,只能靠算法求解。那怎么求?用刚才讲过的暴力求解?那样的话我们分
这一次没有意义,因此我们需要递归地继续分
下去。
看一下算法源码(Java):
public class maxVlaueSum2 {
public static void main(String[] args) {
// 初始化数组
int[] array = {-2,11,-4,13,-5,-2};
System.out.println(maxSum(array,0,array.length-1));
}
/**
* 递归求解函数
* @param array :传入要解决的数组
* @param left :要解决子问题的左界
* @param right :要解决子问题的右界
* @return :返回三个部分的最大值
*/
public static int maxSum(int[] array,int left,int right) {
// BASE情况,如果递归到了最后一层,那么返回合适的值,小于0返回0,大于0返回该数。
if(left == right) {
if (array[left] > 0)
return array[left];
else return 0;
}
int center = (left+right)/2; // 取得数组中点
int leftSubSUM = maxSum(array,left,center); // 子问题左半部分最优解
int rightSubSUM = maxSum(array,center+1,right); // 子问题右半部分最优解
int leftNowSum = 0, maxleftSum = 0; // 当前问题左半部分等待求和的当前值以及最大值
// 遍历求包含左半部分最后一个值的最大值
for (int i=center;i>=left;i--) {
leftNowSum += array[i];
if (leftNowSum > maxleftSum) {
maxleftSum = leftNowSum;
}
}
int rightNowSum = 0,maxrightSum = 0; // 当前问题右半部分等待求和的当前值以及最大值
// 遍历求包含右半部分的第一个值的最大值
for (int j=center+1;j<=right;j++) {
rightNowSum += array[j];
if (rightNowSum>maxrightSum) {
maxrightSum = rightNowSum;
}
}
// 返回当前问题的左半部分最大值、右半部分的最大值和横跨两部分最大值的最大值。即为当前问题的解。
return Math.max(Math.max(leftSubSUM,rightSubSUM),(maxleftSum+maxrightSum));
}
}
源码(Python):
def maxSUM(array,left,right):
if left==right:
if array[left] > 0:
return array[left]
else:
return 0
center = int((left+right)/2)
leftSubSUM = maxSUM(array,left,center)
rightSubSUM = maxSUM(array,center+1,right)
leftNowNum,maxleftSum = 0,0
for i in reversed(range(left,center+1)):
leftNowNum += array[i]
if leftNowNum > maxleftSum:
maxleftSum = leftNowNum
rightNowNum,maxrightSum = 0,0
for i in range(center+1,right+1):
rightNowNum += array[i]
if rightNowNum > maxrightSum:
maxrightSum = rightNowNum
return max(leftSubSUM,rightSubSUM,maxleftSum+maxrightSum)
if __name__ == '__main__':
array = [-2,11,-4,13,-5,-2]
print(maxSUM(array,0,len(array)-1))
两个程序的原理相同,运行正确,不过鄙人水平有限,如果各位发现有问题还烦请指出,谢谢!
这个算法稍微复杂一点,程序更长一点,不过时间复杂度只有O( N log N N\log{N} NlogN),相比**O( N 2 N^{2} N2)**已经好了很多,不过事实上还有一种更加有效且巧妙的算法。
算法3
这个算法我也不清楚它的名字,不过它确是最好的,首先看源代码:
源码(Java):
public class maxValueSum3 {
public static void main(String[] args) {
int[] array = {-2,11,-4,13,-5,-2};
System.out.println(maxSubSum(array));
}
public static int maxSubSum(int [] array) {
int maxSum = 0,NowSum = 0;
for (int i = 0; i < array.length; i++) {
NowSum += array[i];
if (NowSum > maxSum)
maxSum = NowSum;
else if (NowSum < 0)
NowSum = 0;
}
return maxSum;
}
}
源码(Python):
def maxSubSum(array):
maxSum,NowSum = 0,0
for i in range(len(array)):
NowSum += array[i]
if NowSum > maxSum:
maxSum = NowSum
elif NowSum < 0:
NowSum = 0
return maxSum
if __name__ == '__main__':
array = [-2,11,-4,13,-5,-2]
print(maxSubSum(array))
代码很短,不过要想清楚原理可能比之前都稍微难点。它是正确可行的,并且不难看出时间复杂度为O(N),它的效率很高,不过到底是怎么实现的呢?
那我宁哥就用蹩脚的话来简单分享下我的理解吧。
首先可以看到,该算法只是简单遍历了一遍数组而已,就找到了最大子序列和。为什么可以这样?首先,任何负的子序列不可能是最优子序列的前缀。除非数组中的所有值均为负数,只要有一个正数,那么最优自序列的和的前缀一定不可能是负数。因此这也就说明了代码中的else if(NowSum<0) NowSUm=0;
了,也就是如果当前子序列的和为负值,那么初始化为0,它已经不可能是最有子序列的前缀了,就把它的记录抛弃了,如果当前位置在N,那问题就变成了求array[N+1]-array[array.length-1]的最有子序列了。
然后还有一个判断语句if(NowSum > maxSum) maxSum = thisSum;
,看起来很简单,意思就是如果当前子序列和大于记录的最大值,那就更新。可如果放到整个程序里如何保证能正确运行呢?
对一个子序列,如果是每一个数都递增或者递减比较好理解,递增的话最大子序列值就是从第一个大于0的数开始到整个序列,如果是递减的话就是第一个数开始到第一个小于等于0之前的那个数。
还有两种情况,子序列整体递增或整体减小,但是数大大小小不确定。如果子序列整体是增加的,也好理解,仍然是整个序列值呗,那如果子序列是整体递减呢?如果整体是递减的,说明刚开始某个地方会有一个最大值,那样的话我们的方法在刚开始就已经把序列最大值记录了,后面只是更新而已,哈哈哈突然解决。明白了吗?