一维和二维子数组之和最大值

一、约定

  1. 所谓子数组,是连续的。
  2. 只求和,不返回子数组的具体位置。
  3. 元素是整数,所以数组可能包含正整数,0,负数。

二、一维数组子数组之和的最大值

  1. 最直接的求法——暴力求解

           记sum[ i,...j ]为数组A中第i个元素到第j个元素的和(其中0<=i<=j<n)遍历所有可能的sum[ i,...j ]。

/**
 * 常规解法求一位最大字段和
 * @author DaiSong
 * @Date 2013年12月2日
 */
public class OneDimensionalWithNormalSolution {
	
	/**方法一,复杂度O(N^3)
	 * @param a
	 * @param n
	 * @return
	 */
	public static int MaxSum1(int[] a,int n){
		
		int maximum=Integer.MIN_VALUE;
		int sum;
		for(int i=0;i<n;i++){
			for(int j=i;j<n;j++){
				sum=0;
				for(int k=i;k<=j;k++){
					sum+=a[k];
				}
				if(sum>maximum){
					maximum=sum;
				}
			}
		}
		return maximum;
	}
	
	/**
	 * 方法二,改进:将算法的最后一个for循环省略,避免重复计算,复杂度O(N^2).
	 * @param a
	 * @param n
	 * @return
	 */
	public static int MaxSum2(int[] a,int n){
		int maximum=Integer.MIN_VALUE;
		int sum;
		for(int i=0;i<n;i++){
			sum=0;
			for(int j=i;j<n;j++){
				sum+=a[j];
				if(sum>maximum){
					maximum=sum;
				}
			}
		}
		return maximum;
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int a[]={0,-2,3,5,-1,2};
		int b[]={1,-2,3,5,-3,2};
		int c[]={-9,-2,-3,-5,-3};
		//MaxSum1 Test Result
		System.out.println(MaxSum1(a, 6)+" "+MaxSum1(b, 6)+" "+MaxSum1(c, 5));
		//MaxSum2 Test Result
		System.out.println(MaxSum2(a, 6)+" "+MaxSum2(b, 6)+" "+MaxSum2(c, 5));
	}
}

       2.递归法

          将数组(A[0],...A[n-1])分为长度相等的两端数组(A[0],...A[n/2-1])和(A[n/2],...,A[N-1]),分别求出这两端数组各自的最大字段和,则数组(A[0],...A[n-1])的最大字段和为以下三种情况的最大值:

  1. (A[0],...A[n-1])的最大字段和与(A[0],...A[n/2-1])的最大字段和相同。
  2. (A[0],...A[n-1])的最大字段和与(A[n/2],...,A[N-1])的最大字段和相同。
  3. (A[0],...A[n-1])的最大字段和跨过其中间两个元素A[n/2-1]到A[n/2]。

/**
 * 分治策略求一维最大字段和,时间复杂度为O(N*log2N),(以2为底)
 * @author DaiSong
 * @Date 2013年12月2日
 */
public class OneDimensionalWithDivideAndConquer {
	
	public static int FindMaxSubArray(int[] a ,int low,int high){
		int leftSum=0,rightSum=0,crossSum=0;
		if(low==high){
			return a[low];
		}
		int mid=(low+high)/2;
		leftSum=FindMaxSubArray(a,low,mid);
		rightSum=FindMaxSubArray(a,mid+1,high);
		crossSum=FindMaxCrossSubArray(a,low,mid,high);
		return Math.max(Math.max(leftSum, rightSum),crossSum);
	}
	
	/**
	 * 找到跨越终点的子数组的最大值
	 * @param a
	 * @param low
	 * @param mid
	 * @param high
	 * @return
	 */
	public static int FindMaxCrossSubArray(int[] a,int low ,int mid,int high){
		int leftSum=Integer.MIN_VALUE;
		int rightSum=Integer.MIN_VALUE;
		int sum=0;
		for(int i=mid;i>=low;i--){
			sum+=a[i];
			if(sum>leftSum){
				leftSum=sum;
			}
		}
		sum=0;
		for(int i=mid+1;i<=high;i++){
			sum+=a[i];
			if(sum>rightSum){
				rightSum=sum;
			}
		}
		return leftSum+rightSum;
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int a[]={0,-2,3,5,-1,2};
		int b[]={1,-2,3,5,-3,2};
		int c[]={-9,-2,-3,-5,-3};
		System.out.println(FindMaxSubArray(a,0,5));
		System.out.println(FindMaxSubArray(b,0,5));
		System.out.println(FindMaxSubArray(c,0,4));
	}

}

    3.DP

       考虑数组第一个元素A[0],以及最大的一段数组(A[ i ],...,A[ j ])之间的关系。有以下几种情况:

  1. 当0=i=j是,元素本身构成和的最大的一段。
  2. 当0=i<j,和最大的一段以A[0]开始。
  3. 当0<i时,元素A[0]跟和最大的一段没有关系。

/**
 * DP求解一维最大字段和问题。
 * @author DaiSong
 * @Date 2013年12月2日
 */
public class OneDimensionalWithDP {
	
	/**
	 * 方法一,逆序。时间复杂度O(N),空间复杂度O(N).
	 * @param a
	 * @param n
	 * @return
	 */
	public static int MaxSumDp1(int[] a,int n){
		int[] start=new int[n];
		int[] all=new int[n];
		start[n-1]=all[n-1]=a[n-1];
		for(int i=n-2;i>=0;i--){
			start[i]=Math.max(a[i], a[i]+start[i+1]);
			all[i]=Math.max(start[i],all[i+1]);
		}
		return all[0];
	}
	
	/**
	 * 方法二,验证正序和逆序没有差别
	 * @param a
	 * @param n
	 * @return
	 */
	public static int MaxSumDp2(int[] a,int n){
		int[] start=new int[n];
		int[] all=new int[n];
		start[0]=all[0]=a[0];
		for(int i=1;i<n;i++){
			start[i]=Math.max(a[i], a[i]+start[i-1]);
			all[i]=Math.max(start[i],all[i-1]);
		}
		return all[n-1];
	}
	
	/**
	 * 方法三,空间复杂度进一步改进为O(N).
	 * @param a
	 * @param n
	 * @return
	 */
	public static int MaxSumDp3(int[] a,int n){
		int start,all;
		start=all=a[0];
		for(int i=1;i<n;i++){
			start=Math.max(a[i], a[i]+start);
			all=Math.max(start,all);
		}
		return all;
	}
	
	/**
	 * 方法四,方法三的另一种写法。
	 * @param a
	 * @param n
	 * @return
	 */
	public static int MaxSumDp4(int[] a,int n){
		int start,all;
		start=all=a[0];
		for(int i=1;i<n;i++){
			if(start<0){
				start=0;
			}
			start+=a[i];
			if(start>all){
				all=start;
			}
		}
		return all;
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int a[]={0,-2,3,5,-1,2};
		int b[]={1,-2,3,5,-3,2};
		int c[]={-9,-2,-3,-5,-3};
		System.out.println(MaxSumDp1(a,6)+" "+MaxSumDp1(b,6)+" "+MaxSumDp1(c,5));
		System.out.println(MaxSumDp2(a,6)+" "+MaxSumDp2(b,6)+" "+MaxSumDp2(c,5));
		System.out.println(MaxSumDp3(a,6)+" "+MaxSumDp3(b,6)+" "+MaxSumDp3(c,5));
		System.out.println(MaxSumDp4(a,6)+" "+MaxSumDp4(b,6)+" "+MaxSumDp4(c,5));
	}
}



三、二维数组的最大子数组和的最大值

      1.暴力求解

         枚举矩阵的四个点,再就矩阵内的数的和。矩阵求和由于存在重复计算,可以预处理用数组存起来。

/**
 * 暴力解法及其优化求解二维最大子段和。
 * @author DaiSong
 * @Date 2013年12月2日
 */
public class TwoDimensionalWithNormalSolution {
	static int MAX = 501;
	static int[][] ps = new int[MAX][MAX];

	/**
	 * 方法一,暴力求解,时间复杂度为O(N^3*M^3)
	 * 
	 * @param a
	 * @param m
	 * @param n
	 * @return
	 */
	public static int MaxSum1(int a[][], int m, int n) {

		int max = Integer.MIN_VALUE;
		int sum;
		for (int min_i = 0; min_i < n; min_i++) {
			for (int max_i = min_i; max_i < n; max_i++) {
				for (int min_j = 0; min_j < m; min_j++) {
					for (int max_j = min_j; max_j < m; max_j++) {
						// 求区域矩阵的和
						sum = 0;
						for (int i = min_i; i <= max_i; i++) {
							for (int j = min_j; j <= max_j; j++) {
								sum += a[i][j];
							}
						}
						if (sum > max) {
							max = sum;
						}
					}
				}
			}
		}
		return max;
	}

	/**
	 * 方法二,改进:考虑到区域的和需要频繁计算,做了预处理。用ps[n][m]存放i=1..n,j=1..m区域和。
	 * 时间复杂度为O(N^2*M^2)
	 * @param a
	 * @param n
	 * @param m
	 * @return
	 */
	public static int MaxSum2(int a[][], int n, int m) {

		int max = Integer.MIN_VALUE;
		int sum;
		PieceSum(a, n, m);
		for (int min_i = 1; min_i <= n; min_i++) {
			for (int max_i = min_i; max_i <= n; max_i++) {
				for (int min_j = 1; min_j <= m; min_j++) {
					for (int max_j = min_j; max_j <= m; max_j++) {
						// 求区域矩阵的和
						sum = ps[max_i][max_j]-ps[min_i-1][max_j]-ps[max_i][min_j-1]+ps[min_i-1][min_j-1];
						if (sum > max) {
							max = sum;
						}
					}
				}
			}
		}
		return max;
	}

	/**
	 * 预处理求出区域和,时间复杂度为O(N*M)
	 * @param a
	 * @param n
	 * @param m
	 */
	public static void PieceSum(int[][] a, int n, int m) {
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				ps[i][j] = ps[i - 1][j] + ps[i][j - 1] - ps[i - 1][j - 1]
						+ a[i][j];
			}
		}
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int a[][] = { { -1, -4, 3 }, { 3, 4, -1 }, { -5, -2, 8 } };
		System.out.println(MaxSum1(a, 3, 3));
		int b[][] = { {0,0,0,0},{ 0, -1, -4, 3 }, { 0, 3, 4, -1 }, { 0, -5, -2, 8 } };
		System.out.println(MaxSum2(b, 3, 3));
	}
}

         2.DP

          把二维数组压缩为一维数组,再求和。

/**
 * 最大子阵,压缩矩阵为一维数组,转化为求最大字段和问题,运用动态规划求解。
 * @author DaiSong
 * @Date 2013年12月2日
 */
public class TwoDimensionalWithDP {
	 
	 /**转化为一位数组,求一维数组的最大字段和。时间复杂度O(N*M*Min(N,M))
	 * @param a
	 * @param n
	 * @param m
	 * @return
	 */
	public static int TwoMaxSum(int a[][],int n,int m){
		 int minMax;
		 int  Max = Integer.MIN_VALUE;  
	     for (int i=0; i<n; i++){
	         minMax = OneMaxSum(a[i], m);  
	         if (minMax > Max)
	        	 Max = minMax;  
	         for (int j=i+1; j<n; j++){  
	             for (int k=0; k<n; k++){  
	                 a[i][k] += a[j][k];  
	             }  
	             minMax = OneMaxSum(a[i], n);  
	             if (minMax > Max)
	            	 Max = minMax;  
	         }  
	     }  
	     return Max;
	 }
	 
	 /**一维最大字段和
	 * @param a
	 * @param n
	 * @return
	 */
	public static int OneMaxSum(int[] a,int n){
			int start,all;
			start=all=a[0];
			for(int i=1;i<n;i++){
				start=Math.max(a[i], a[i]+start);
				all=Math.max(start,all);
			}
			return all;
		}
	
	 public static void main(String[] args) {
		 int[][] a ={{-1,-4,3},{3,4,-1},{-5,-2,8}};
	     System.out.print(TwoMaxSum(a,3,3));  
	 }  
}


参考资料:《编程之美》,《算法导论》。

www.gavinboo.com同步发布。

版权声明:本文为博主原创文章,未经博主允许不得转载。

转载于:https://www.cnblogs.com/AndyDai/p/4734108.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值