求数组的一个和最大的子数组

    假设现在我们需要求数组的一个和最大的子数组,比如对于数组{-1,1,3,-1},和最大的子数组就是{1,3}。我们来设计一下这样的算法。

一、暴力法

    首先我们可能会想到使用暴力法两层循环来遍历数组求解,外层循环表示子数组的开始位置,内层循环表示子数组的结尾位置,一一遍历,最后得到最大的一个和以及子数组的开头和结尾,我们使用Java来实现:

    public static int[] getMaxBonusByVlWay(int[] arr){
        int maxBonus = arr[0], maxLeftLocation = 0, maxRightLocation = 0;
        int sum;
        int[] result = new int[3];
        for(int i = 0; i<arr.length; i++){
            sum = 0;
            for(int j = i; j<arr.length; j++){
                sum += arr[j];
                if(sum > maxBonus){
                    maxBonus = sum;
                    maxLeftLocation = i;
                    maxRightLocation = j;
                }
            }
        }
        result[0] = maxLeftLocation;
        result[1] = maxRightLocation;
        result[2] = maxBonus;
        return result;
    }
    这个算法的时间复杂度很明显为O(n^2),有没有其他时间复杂度小一点的思路呢?


二、分治法

    事实上我们可以使用分治法来递归实现这一需求,至于如何分治呢?

    1、分割。我们可以把这个范围为[low,high]的数组arr分为[low,mid]和[mid+1,high]两部分。

    2、治理。对于每个需要求其最大子数组和的范围为[low,high]的数组arr,我们都把他分为[low,mid],[mid+1,high]。如果我们需要找到[low,high]这个范围的数组的最大子数组和,我们可以从三个地方来查找,然后取最大值。这个子数组可能位于[low,mid],[mid+1,high],以及跨越了mid,共三种可能,从中取最大值。直到分到只有一个元素,也就是low==high,我们直接把它的最大子数组和视为arr[low]。

    3、合并。我们前面分到不能再分的之后,进行组合,最终找到最大的子数组和。

    我们来实现这一思路:

    //计算出跨mid的一个最大连续子数组和
    private static int[] getMaxBonusCrossingMid(int[] arr, int low, int mid, int high){
        if(low>mid||mid>high||low<0||high>arr.length)
            throw new IllegalArgumentException("非法参数");
        int[] result = new int[3];
        int leftSum = Integer.MIN_VALUE;
        int sum = 0;
        int maxLeftLocation = 0;
        for(int i = mid;i>=low; i--){
            sum+=arr[i];
            if(sum>leftSum){
                leftSum = sum;
                maxLeftLocation = i;
            }
        }
        
        int rightSum = Integer.MIN_VALUE;
        int maxRightLocation = 0;
        sum = 0;
        for(int i = mid+1 ;i<=high ;i++){
            sum+=arr[i];
            if(sum>rightSum){
                rightSum = sum;
                maxRightLocation = i;
            }
        }
        result[0] = maxLeftLocation;
        result[1] = maxRightLocation;
        result[2] = leftSum + rightSum;
        return result;
    }
    //递归计算一个数组的一个最大连续子数组和
    public static int[] getMaxBonus(int[] arr, int low, int high){
        if(low>high)
            throw new IllegalArgumentException("非法参数");
        if(low == high){
            int[] result = new int[3];
            result[0] = low;
            result[1] = high;
            result[2] = arr[low];
            return result;
        }
        int[] leftResult = new int[3];
        int[] rightResult = new int[3];
        int[] crossingResult = new int[3];
        int mid = (low + high) >> 1;
        leftResult = getMaxBonus(arr, low, mid);
        rightResult = getMaxBonus(arr, mid+1, high);
        crossingResult = getMaxBonusCrossingMid(arr, low, mid, high);
        if(leftResult[2]>rightResult[2]&&leftResult[2]>crossingResult[2])
            return leftResult;
        else if(rightResult[2]>leftResult[2]&&rightResult[2]>crossingResult[2])
            return rightResult;
        else
            return crossingResult;
    }
    算法的关键在于,每当我们把一个范围为[low,high]的数组分为[low,mid],[mid+1,high],其中mid=(low+high)/2之后,我们需要从[low,mid],[mid,high]以及跨过mid的三个范围分别找到和最大的子数组,选出和最大的那个。这个算法很明显是二分递归,时间复杂度为O(nlgn)。时间复杂度明显优于前面的暴力求解法。


三、扫描法

    遍历,之前的是负和抛弃之前的结果,重新积累,期间保留最大值,时间复杂度O(n):

    public int getMaxBonus(int[] array) {
        int sum = array[0];
        int result = Integer.MIN_VALUE;
        for(int i = 1; i < array.length; i++){
            if(sum>0)
                sum+=array[i];//前面的大于0,就加上面前的 否则舍弃
            else
                sum = array[i];
            if(sum>result)
                result = sum;
        }
        return result;
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值