zoukankan      html  css  js  c++  java
  • 一维和二维子数组之和最大值

    一、约定

    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同步发布。

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

  • 相关阅读:
    bzoj 3243: [Noi2013]向量内积
    bzoj 4818: [Sdoi2017]序列计数
    AtCoder Grand Contest 023 F
    bzoj 4573: [Zjoi2016]大森林
    bzoj 5305: [Haoi2018]苹果树
    bzoj 5298: [Cqoi2018]交错序列
    codeforces496C
    codeforces534B
    牛客小白月赛13
    codeforces605A
  • 原文地址:https://www.cnblogs.com/AndyDai/p/4734108.html
Copyright © 2011-2022 走看看