最大子数组问题

问题描述:

给定一数组ary,其元素有正有负,寻找ary的和最大的连续子数组,并返回其和。

例如数组:

int[] ary = {-3,5,-1,-2,4,-1};

最大连续子数组为:{5,-1,-2,4},其和为6。

实际上最大子数组问题在算法导论上引申自买股票获最大收益的问题,将来的股票价格会以预期价格的形式给出作为股票购买者的参考价。

数组元素可视为股票价格在某一天相对于前一天的价格的波动,结合例子中的数组,假定股票第0天的价格为50元,第一天的价格波动为-3,那么第一天的股价为47;第二天的价格波动为5(相对第一天),那么第二天的价格为52元,以此类推。。。

低买高卖可获得收益,最大收益的情况为卖出价与买入价之间的价差最大。

在买入和卖出期间的股票的波动价格看做一个子数组,如果能保证某子数组的和为整个数组的所有子数组和最大,那么股票购买者选择在这段时间持有股票可获得最大的收益。

分析:

暴力求解是穷举所有的子数组,寻找和最大的子数组。N个元素的子集问题的规模为2^N,要穷举2^N个子集,复杂度太高。

可以考虑分治算法思想。

将数组ary拆分成左右两个子数组,拆分的方法就是常用mid=(low+high)/2的方式。但是,这里的拆分的意义不同于二分查找的拆分,这里拆分是将数组分成左右两个部分,并不存在排除掉ary[mid]的含义。ary[mid]是被划分为左子数组的一部分。

ary的最大子数组必然存在于以下三种情况之一:

A、完全位于左边的子数组:ary[low,mid]

B、完全位于右边的子数组:ary[mid+1,high]

C、跨越了左边和右边的子数组,这种情况的隐含条件在于, 左子数组ary[low,mid]必然至少有一个元素在最大子数组中,也即使可以肯定ary[mid]必然在最大子数组中,否则不满足“跨越”的前提条件而回到情况B;同理,右子数组ary[mid+1,high]必然至少有一个元素在最大子数组中,也即是可以肯定ary[mid+1]必然在最大子数组中。

三种情况的最大子数组必然是这个数组的最大子数组。

遵循以上过程,递归调用求解。

递归基为low与high相等的情况,其含义很明确:单元组的最大子数组必然是其自身。

分治算法实现:

package agother;
import java.util.Arrays;
public class SubArray {
    private static int getMax(int ...ary){
        Arrays.sort(ary);
        return ary[ary.length-1];
    }
    /*最大子数组跨越了左右两个子数组*/
    private static int getMaxSumOfCrossSubArray(int[] ary,int low,int mid,int high){
        int tmpLeftSum = 0,tmpRightSum = 0;
        int leftSum = ary[mid],rightSum = ary[mid+1];//这种情况下,最大子数组至少包含ary[mid]和ary[mid+1]两个元素,才能实现“跨越”
        for (int i = mid; i >= low; i--) {//处理子数组的左边部分
            tmpLeftSum += ary[i];
            if (leftSum < tmpLeftSum) {
                leftSum = tmpLeftSum;
            }
        }
        for (int j = mid+1; j <= high; j++) {//处理子数组的右边部分
            tmpRightSum += ary[j];
            if (rightSum < tmpRightSum) {
                rightSum = tmpRightSum;
            }
        }
        return leftSum+rightSum;
    }
    public static int getMaxSumOfSubArray(int[] ary,int low,int high){
        if (high == low) {return ary[low];}//递归基
        int mid = (high+low)/2;
        int leftMaxSum =  getMaxSumOfSubArray(ary,low,mid);//最大子数组位于左子数组中
        int crossMaxSum = getMaxSumOfCrossSubArray(ary,low,mid,high);//最大子数组跨越了左右两个子数组
        int rightMaxSum = getMaxSumOfSubArray(ary,mid+1,high);//最大子数组位于左子数组中
        return getMax(leftMaxSum,crossMaxSum,rightMaxSum);
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        try {
            int[] ary = {-3,5,-1,-2,4,-1};
            int result = getMaxSumOfSubArray(ary,0,ary.length-1);
            System.out.println(result);
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}
原文地址:https://www.cnblogs.com/qcblog/p/7758508.html