一、约定
- 所谓子数组,是连续的。
- 只求和,不返回子数组的具体位置。
- 元素是整数,所以数组可能包含正整数,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同步发布。
版权声明:本文为博主原创文章,未经博主允许不得转载。