【剑指offer】面试题42:连续子数组的最大和

题目:输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)。

牛客网链接:https://www.nowcoder.com/questionTerminal/459bd355da1549fa8a49e350bf3df484

 例如输入的数组为{1,-2,3,10,-4,7,2,-5 },和最大的子数组为{3,10,-4,7,2},因此输出为该子数组的和为18。

看到该题目,很多人都能想到最直观的方法,即枚举出数组的所有子数组并求出他们的和。一个长度为n的数组,总共有n(n+1)/2 个子数组。计算出所有的子数组的和,最快也需要O(n^{2})的时间。通常最直观的方法不会是最优的方法,面试官将提示我们还有更快的方法。

1、举例分析数组的规律

 我们试着从头尾逐个累加示例数组中的每个数字。初始化和为0.第一步加上第一个数字,此时和为1.接下来第二步加上数字-2,和就变成了-1.第三步加上数字3.我们注意到由于此前累计的和为-1,小于0,那如果用-1加3,得到的和为2,比3本身还小。也就是说从第一个数字开始的子数组的和会小于从第三个数字开始的子数组的和。因此我们不用考虑从第一个子数组,之前累计的和也被抛弃。

我们从第三个数字重新开始累加,此时得到的和为3.接下来第四步加10,得到和为13.第五步加上-4,和为9.我们发现-4是一个负数,因此累加-4之后得到的和比原来的还小。因此我们要把之前得到的和13保存下来,它有可能是最大的子数组的和。第六步加上数字7,9加7的结果是16,此时和比之前最大的和13还要大,把最大的子数组的和由13更新为16.第七步加上2,累加得到的和为18,同时我们也要更新最大子数组的和。第八步加上最后一个数字-5,由于得到结果为13,小于此前得到的最大和18,因此最终最大的子数组的和为18,对应的子数组是{3,10,-4,7,2}。

【剑指offer】面试题42:连续子数组的最大和_第1张图片

代码实现如下:

package com.zju.offer.arrays;

/**
 * 连续子数组的最大和
 */
public class FindMaxSumOfSubArray {

	public int findMaxSumOfSubArray(int[] array){
		if(array.length == 0){
			return 0;
		}
		
		int greatest = 0x80000000;
		int curSum = 0;
		for (int i = 0; i < array.length; i++) {
			if(curSum <= 0){
				// 如果curSum为负数,则将surSum更新为array[i]
				curSum = array[i];
			}else{
				// 如果curSum为正数,则将array[i]累加到curSum
				curSum += array[i];
			}
			
			if(curSum > greatest){
				// 更新最大值
				greatest = curSum;
			}
		}
		return greatest;
	}
	
	// 测试
	public static void main(String[] args) {
		FindMaxSumOfSubArray find = new FindMaxSumOfSubArray();
		int[] array = {-1,-2,-3,-10,-4,-7,2,-5};
		int maxSumOfSubArray = find.findMaxSumOfSubArray(array);
		System.out.println(maxSumOfSubArray);
	}
}

解法二:应用动态规划法

我们还可以适用动态规划的思想来分析这个问题。如果用函数 f(i) 表示以第 i 个数字结尾的子数组的最大和,那么我们需要求出 max[f(i)],其中0 <= i < n。我们可以使用能够下面的递归公示求f(i):

【剑指offer】面试题42:连续子数组的最大和_第2张图片

下面采用牛客网上的一个答案分析:

F(i):以 array[i] 为末尾元素的子元素数组的和的最大值,子数组的元素的相对位置不变;

  • F(i) = max(F(i - 1) + array[i], array[i])

res:所有子数组的和的最大值

  • res = max(res, F(i))

如数组:{6,-3,-2,7,-15,1,2,2}

  • 1.  初始状态:

F(0) = 6;

res = 6;

  • 2.  i = 1:

F(1) = max(F(0) - 3, -3) = max(6-3,  -3) = 3;

res = max(F(1), res) = max(3, 6) = 6;

  • 3.  i = 2:

F(2) = max(F(1) - 2,  -2) = max(3 - 2,  -2) = 1;

res = max(F(2),  res) = max(1, 6) = 6;

  • 4.  i = 3:

F(3) = max(F(2) + 7, 7) = max(1 + 7, 7) = 8;

res = max(F(3),res) = max(6, 8) = 8;

  • 5.  i = 4:

F(4) = max(F(3) - 15,  -15) = max(8 - 15,  -15) = -7;

res = max(F(4),  res) = max(-7, 8) = 8;

......以此类推,最终 res 的值为8。

package com.zju.offer.arrays;

/**
 * 连续子数组的最大和
 */
public class FindMaxSumOfSubArray {
	
	// 使用动态规划法实现
	public int findGreatestSunOfSubArray(int[] array){
		if(array.length == 0){
			return 0;
		}
		
		// 记录当前子数组中的最大值
		int res = array[0];
		// 包含array[i]的连续数组的最大值
		int max = array[0];
		
		for (int i = 1; i < array.length; i++) {
			max = Math.max(max + array[i], array[i]);
			res = Math.max(max, res);
		}
		return res;
	}
	
	// 测试
	public static void main(String[] args) {
		FindMaxSumOfSubArray find = new FindMaxSumOfSubArray();
		int[] array = {-1,-2,-3,-10,-4,-7,2,-5};
		int maxSumOfSubArray = find.findGreatestSunOfSubArray(array);
		System.out.println(maxSumOfSubArray);
	}
}

 

你可能感兴趣的:(剑指offer,搞定剑指Offer)