一、约定
- 所谓子数组,是连续的。
- 只求和,不返回子数组的具体位置。
- 元素是整数,所以数组可能包含正整数,0,负数。
二、一维数组子数组之和的最大值
- 最直接的求法——暴力求解
记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])的最大字段和为以下三种情况的最大值:
- (A[0],...A[n-1])的最大字段和与(A[0],...A[n/2-1])的最大字段和相同。
- (A[0],...A[n-1])的最大字段和与(A[n/2],...,A[N-1])的最大字段和相同。
- (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 ])之间的关系。有以下几种情况:
- 当0=i=j是,元素本身构成和的最大的一段。
- 当0=i<j,和最大的一段以A[0]开始。
- 当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同步发布。
版权声明:本文为博主原创文章,未经博主允许不得转载。