最大子数组之和

问题描述

一个含有n个元素的整数数组,数组元素可以是正数也可以是负数,数组中连续的一个或多个元素称为改数组的子数组,子数组所有元素之和称为子数组和,求一个整数数组子数组和的最大值。例如数组array={1,-2,4,-3,5,-6},那么数组array的最大子数组和为: 4+(-3)+5=6。

分析求解

方法一:蛮力法

最容易想到,也是最简单的想法就是遍历这个数组的所有子数组,比较得出最大子数组的和。思路是:

子序列长度为1的所有子数组有n个:{a0},{a1},···,{an}

子序列长度为2的所有子数组有n-1个:{a0 , a1},{a1 , a2},···,{an-1 , an}

···

子序列长度为n的所有子数组有1个:{a0 , a1 , ··· , an}

算法:

	/**
	 * get max sum of an array's subsequence by Brute Force.
	 */
	public static int getMaxSubArrayBF(int[] array) {
		int maxSum = Integer.MIN_VALUE;
		int tmpSum = 0;
		/**
		 * i : 子数组长度
		 * j : 子数组起始位置
		 */
		for (int i = 1; i <= array.length; i++) {
			for (int j = 0; j < array.length - i + 1; j++) {
				tmpSum = 0;
				for (int k = 0; k < i; k++) {
					tmpSum += array[j + k];
					if(maxSum < tmpSum)
						maxSum = tmpSum;
				}
			}
		}
		return maxSum;
	}
算法时间复杂度为O(n^3)。

方法二:改进蛮力法

方法一最明显的一个缺点就是重复计算,例如sum[i , j] = sum[i , j - 1] + array[j],也就是说计算较长的子数组和会用到较短子数组和,因此可以在计算子数组之和时重复利用已计算的子数组之和,减小时间复杂度。

算法:

	/**
	 * get max sum of an array's subsequence by Majorizing Brute Force.
	 */
	public static int getMaxSubArrayMBF(int[] array) {
		int maxSum = Integer.MIN_VALUE;
		int tmpSum = 0;
		// i : 子数组起始位置
		// j : 子数组结束位置
		for (int i = 0; i < array.length; i++) {
			tmpSum = 0;
			for (int j = i; j < array.length; j++) {
				tmpSum += array[j];
				if(maxSum < tmpSum)
					maxSum = tmpSum;
			}
		}
		return maxSum;
	}
虽然还是蛮力法,但算法时间复杂度为O(n^2)。

方法三:动态规划

可以采用动态规划的方法来求解这一问题,首先以数组最后一个元素array[n-1]为例说明与最大子数组的关系,有两种情况:

  1. 最大子数组包含元素array[n - 1],即最大子数组是以元素array[n - 1]结尾的;
  2. 最大子数组不包含元素array[n - 1],那么求array[0 , 1 , ··· , n-1]最大子数组问题也就是求其子数组array[0 , 1 , ··· , n - 2]的最大子数组问题。

通过上述分析可知,这里可以将问题分解为规模较小的同类型子问题,而且规模较大问题可能会用到规模较小问题的结果,可以采用动态规划的思想:

记数组array[0 , 1 , ··· , i - 1]中包含其结尾元素array[i - 1]的最大的一段子数组和为endMaxSum[i - 1],因为必然包含元素array[i - 1],所以endMaxSum要么是包含array[i - 1]的一个子数组,要么就是单独一个元素array[i - 1]构成的子数组,因此:

endMaxSum[i - 1] = max{endMaxSum[i - 2] + array[i-1] , array[i - 1]}

假设已经计算出数组array[0 , 1 , ··· , i - 1]的最大子数组和为tmpMaxSum[i - 1],那么数组array[0 , 1 , ··· , i]的最大子数组和:

tmpMaxSum[i] = max{endMaxSum[i] , tmpMaxSum[i - 1]}

算法:

	/**
	 * get max sum of an array's subsequence by Dynamic Programming
	 */
	public static int getMaxSubArrayDP(int[] array) {
		int[] endMaxSum = new int[array.length];
		int[] tmpMaxSum = new int[array.length];
		
		endMaxSum[0] = tmpMaxSum[0] = array[0];
		
		for (int i = 1; i < array.length; i++) {
			endMaxSum[i] = max(endMaxSum[i - 1] + array[i], array[i]);
			tmpMaxSum[i] = max(endMaxSum[i], tmpMaxSum[i - 1]);
		}
		return tmpMaxSum[array.length - 1];
	}
	
	private static int max(int a, int b) {
		return a > b ? a : b;
	}

算法时间复杂度为O(n)。该算法需要注意的是endMaxSum[i - 1]有两个特征:一定包含结尾元素array[i - 1],一定是一个连续的子数组(一个元素也可以构成子数组)。

方法四:改进动态规划

方法三有两个缺点:第一是使用了两个长度为n的数组endMaxSum和tmpMaxSum,增加了空间开销;第二是只能计算最大子数组之和,而无法得到最大子数组的位置。通过分析方法三的两个公式就很容易得出改进思路。

算法:

	/**
	 * get max sum of an array's subsequence by Majorizing Dynamic Programming
	 */
	public static int getMaxSubArrayMDP(int[] array) {
		int startPos = 0;
		int endPos = 0;

		//相当于方法三中的tmpMaxSum
		int maxSum = array[0];
		//相当于方法三中的endMaxSum
		int endMaxSum = array[0];
		
		for (int i = 1; i < array.length; i++) {
			if(endMaxSum > 0) {
				endMaxSum = endMaxSum + array[i];
				if(endMaxSum > maxSum) {
					maxSum = endMaxSum;
					endPos = i;
				}
			}
			else {
				endMaxSum = array[i];
				if(endMaxSum > maxSum) {
					maxSum = endMaxSum;
					startPos = i;
					endPos = i;
				}
			}
		}
		System.out.println("From " + startPos + " to " + endPos);
		return maxSum;
	}
算法时间复杂度仍然是O(n),但是空间复杂度降低到O(1),而且可以得到最大子数组的位置。

总结

动态规划法(Dynamic Programming)与分治法类似,是将问题一层一层地分解为规模逐渐减小的同类型的子问题。其与分治法的一个重要不同点在于:用分治法分解后得到的子问题通常都是相互独立的,而用动态规范发分解后得到的子问题很多都是重复的。由于各个子问题相互独立,分治法通常采用递归调用发生而避免重复计算,动态规划法通常采用递推的方法,从规模最小的子问题开始算起,依次计算规模逐渐扩大的子问题,由于计算大子问题时往往要使用小规模子问题的结果,因此每次计算完毕后都要把所得的结果保存起来,按此方法不断扩大计算规模,直至达到所求问题的规模。

若一个问题可以分解为若干个高度重复的子问题,且问题也具有最优子结构性质,就可以用动态规划法求解,以递推的方式逐层计算最优值并记录必要的信息,最后根据记录的信息构造最优解。解题步骤可归纳为:

  1. 找出最优解的性质,并刻画其结构特性;
  2. 递归地定义最优值(写出动态规划方程);
  3. 自底向上(规模从小到大)地递推方式计算出最优值;
  4. 根据计算最优值时得到的信息,以递归方法构造一个最优解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值