这是在《数据结构与算法分析 Java语言描述中文第二版》里的一个问题。本文记载我对书中该问题的第四种解法的理解。
分析:数列的最大子序列必定是以j结尾的,其中,j属于[A1,AN]。首先,我们计算出以j结尾的子序列中的最大子序列的和;然后,因为j有N个值,也就是说第一步得到N最大子序列的和,这N个值中最大的那一个就是最终答案。从N个数中找出最大值是很容易的。关键是,在以j结尾的子序列中,哪一个是最大的。下面来分析解决这个问题。
局部看:
存在子序列:A1,A2,A3...Ai...A(j-1),Aj。我们现在的问题是,求这个序列的以Aj结尾的子序列的和中的最大值?
Sum = Aj + A(j-1) + ...Ai...+ A3 + A2 + A1;
其中,只有Aj是必须的。
我们的目的去掉A1,A2,A3...,使得Sum值最大。我们知道,减去一个负数可以使一个数变大,所以如果A1 + A2 + A3 + ... A(i-1)< 0,那么:
Aj + A(j-1) + ...Ai > Aj + A(j-1) + ...Ai...+ A3 + A2 + A1;
如果Ai + ... + A(k-1) < 0,其中k < j。那么:
Aj + A(j-1) + ...Ak >Aj + A(j-1) + ...Ai;
也就是说,不断去掉和为负的前缀序列,以Aj结尾的子序列的和就会不断变大。当所有和为负的前缀序列都去掉时,以Aj结尾的子序列的和就变到最大了。因为这时,Aj结尾的子序列的任意前缀序列的和都是大于等于0的,减去任意前缀序列最多使和不变,甚至变小。
此时,我们找到了求这个序列的以Aj结尾的子序列的和的最大值的方法,就是去掉序列中所有和为负的前缀序列。
全局看:
Sum(j-1) = A(j-1) + ...Ai...+ A3 + A2 + A1;
Sum(j) = Aj +Sum(j-1);
如果Sum(j-1)< 0,就应该去掉。
结合源代码来做个总结:
public int maxSubSum(int[] a) { int maxSum = 0; int thisSum = 0; for(int j = 0; j < a.length; j++) { thisSum += a[j]; if(thisSum > maxSum) { maxSum = thisSum; } // 和为0的序列不可能是最大子序列的前缀,删掉 if(thisSum < 0) { thisSum = 0; } } return maxSum; }
for循环的每一次迭代计算一个以Aj结尾的序列的最大子序列和thisSum。如果thisSum < 0,这个值将被设为0,也就是说,它所代表的序列不能作为以Aj后面的元素结尾的序列的最大子序列的前缀。按照这个过程,其实,在计算以Aj结尾的序列的最大子序列和thisSum时,Aj所有和为负的前缀序列都去掉了。
书中阐释这个算法正确性的部分的意思是这样的:和为负的序列不可能作为最大子序列的前缀,并且,存在序列Ai,A(i+1),A(i+2),A(i+3)...A(j-1),Aj。如果Ai,A(i+1),A(i+2),A(i+3)...A(j-1),不能作为Aj的前缀序列,那么A(p)...A(j-1)也不能作为Aj的前缀,其中i<p<j。因为如果Ai+A(i+1)+A(i+2)+A(i+3)+ ...+ A(j-1)<0的话,那么A(p)+...+A(j-1)<0也是成立的,因为Ai...A(p-1)作为另一个可能的最大子序列的前缀,该序列的和必定大于等于0。Ai,A(i+1),A(i+2),A(i+3)...A(j-1)少了Ai...A(p-1)不可能使数列的和变大。
我认为从这个解释直接到上面的代码跨度太大了,所以才有上文更仔细的解释。