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

  最近在研究研究算法,打算把学习的些许东西记录下来,一来。巩固学习的东西,而来记录下来后面也方便查找。网上关于这些基础算法的文章很多,仅记录学习过程。

1、直接暴力求解,两重循环,求得结果,时间复杂度θ(n2);记得之前一次找工作的笔试题中就有这个问题,当时看到就蒙了,完全不会(算法太差,尴尬    -_-‘|),就写了一个最笨的方法,暴力求解(当然笔试结果可想而知),算法是这样的:

public static void getMaxSubArr1(SubArrElement el, int[] arr) {
        int currSum = 0;
        for(int i=0; i<arr.length; i++) {
            currSum = arr[i];
            for(int j=i+1; j<arr.length; j++) {
                currSum += arr[j];
                if(el.sum <= currSum) {
                    el.start = i;
                    el.end = j;
                    el.sum = currSum;
                }
            }
        }
     }

2、在算法导论的第4章就讲到用分治策略求解最大连续子数组和问题,分治策略:将一个问题先分解问一些子问题,子问题的形式不发生变化,与原问题形式一致,只是规模变小了,递归求解子问题,最后将子问题的解在组合成原问题的解,该方法求解时间复杂度θ(nlgn),我写了一个如下:

/**
     * 通过分治法一个最大连续子数组和
     * 有三种情况:
     * 1、最大连续子数组都在中点的左边
     * 2、最大连续子数组都在中点的右边
     * 3、穿过了中点,一部分在左边,一部分在右边
     * @param el
     * @param arr
     */
    public static void getMaxSubArr(SubArrElement el, int[] arr, int left, int right) {
        if(left == right) {
            el.start = el.end = left;
            el.sum = arr[left];
            return ;
        }
        SubArrElement leftEl = new SubArrElement();
        SubArrElement rightEl = new SubArrElement();
        SubArrElement midEl = new SubArrElement();
        
        int mid = (right + left) / 2;
        
        //在左边的情况
        getMaxSubArr(leftEl, arr, left, mid);
        //在右边的情况 
        getMaxSubArr(rightEl, arr, mid+1, right);
        //穿过中点的情况
        getMaxSubArrCrossingMid(midEl, arr, left, mid, right);
        
        //在左边
        if(leftEl.sum >= midEl.sum && leftEl.sum >= rightEl.sum) {
            el.sum = leftEl.sum;
            el.start = leftEl.start;
            el.end = leftEl.end;
            return;
        } else if(rightEl.sum >= midEl.sum && rightEl.sum >= midEl.sum) {
            el.sum = rightEl.sum;
            el.start = rightEl.start;
            el.end = rightEl.end;
            return;
        } else {
            el.sum = midEl.sum;
            el.start = midEl.start;
            el.end = midEl.end;
            return;
        }
     }
    
    /**
     * 计算穿过中点的情况
     * @param el
     * @param arr
     * @param left
     * @param mid
     * @param right
     */
    private static void getMaxSubArrCrossingMid(SubArrElement el, int[] arr, int left, int mid, int right) {
        int leftSum = 0, rightSum = 0, tmpSum = 0;
        for(int i=mid; i>left; i--) {
            tmpSum += arr[i];
            if(tmpSum > leftSum) {
                leftSum = tmpSum;
                el.start = i;
            }
        }
        tmpSum = 0;
        for(int i=mid+1; i<right; i++) {
            tmpSum += arr[i];
            if(tmpSum > rightSum) {
                rightSum = tmpSum;
                el.end = i;
            }
        }
        el.sum = leftSum + rightSum;
    }

3、用线性方法求解该问题,可从左至右遍历数组并求和,以currSum记录当前A[1...i]的和,sum记录最大子数组的和,当sum比currSum时,交换并记录开始和结束为止,若currSum小于0,那么代表currSum加上第i个数反而比之前的和数都小了,此时应当重置当前的起始位置currStart、结束位置currEnd和currSum的只,最后再判断sum的直,如果为0,那么说明所有子数组和均小于等于0;此时返回数组中的最大者即可,时间复杂度仅为(n):

/**
     * 线性求一个最大连续子数组和
     * @param el
     * @param arr
     */
    public static void getMaxSubArr(SubArrElement el, int[] arr) {
        int currStart = 0, currEnd = 0;
        int currSum = 0;
        for(int i=0; i<arr.length; i++) {
            currSum += arr[i];
            //当最大值小于当前已计算最大的连续字数组值,交换
            if(el.sum < currSum) {
                el.sum = currSum; 
                //记录当前的结束位置
                currEnd = i;
                //并确定当前最大子数组时的起始位置
                el.start = currStart;
                el.end = currEnd;
            }
            //当计算值小于0时,重置当前起点
            if(currSum <= 0) {
                currSum = 0;
                currStart = currEnd = i+1;
            }
        }
        //如果遍历完数组,最大值还是为0,那么取数组中最大者返回
        if(el.sum == 0) {
            el.sum = arr[0];
            for(int j=1; j<arr.length; j++) {
                if(arr[j] > el.sum) {
                    el.sum = arr[j];
                }
            }
        }
     }

 

以前学东西,看书都不会做笔记,当时似乎懂了,可不多长时间,就都抛之脑后,没有任何效果;俗话说:好记性不如烂笔头(何况记性还不好~_~'),所以决定之后学习的点点滴滴,记录下来为好。

posted @ 2016-03-18 00:04  vindanear  阅读(376)  评论(0编辑  收藏  举报