ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

算法 - 求最大连续子数组和起始下标

2019-10-27 17:01:05  阅读:236  来源: 互联网

标签:endIndex arr 下标 int 起始 minStart 算法 数组 sumEnd


执行代码参见GitHub
https://github.com/GloryXu/algorithm/tree/master/src/main/java/com/redsun/algorithm/subarraysum

逛论坛无意中发现一个算法帖子,一时间也没想出更好的解决方式,搜索了下,整理下。
在这里插入图片描述

暴力破解

    @Override
    public void execute() {
        for (int i = 0; i < arr.length; i++) {
            // 遍历从i开始的所有子数组
            for (int j = i; j < arr.length; j++) {
                int thisSum = 0;

                // 累计求和
                for (int k = i; k <= j; k++) {
                    thisSum += arr[k];
                }
                if (thisSum > maxSum) {
                    // 记录最大子数组的位置和长度
                    maxSum = thisSum;
                    start = i;
                    end = j;
                }
            }
        }
    }

外面两层循环,里面一层循环求和,再进行比较,最后求出一个最大的子数组。
在求出最大子数组同时,记录下对应的start和end位置,即为最大子数组的对应下标。
该种算法的时间复杂度O(n^3)

分治法

代码块1

    private int findMaxChildArr(int[] arr, int left, int right) {
        if (left == right) {
            // 如果左等于右,则说明这个子数组只存在一个元素,则返回它用于比较
            return arr[left];
        } else {
            // 递归思想
            int middle = (left + right) / 2;
            // 寻找左侧数组最大值;
            int leftMax = findMaxChildArr(arr, left, middle);
            // 寻找右侧数组最大值;
            int rightMax = findMaxChildArr(arr, middle + 1, right);
            // 寻找中间最大值
            int middleMax = findMiddleMax(arr, left, right, middle);
            // 比较求出值
            if (leftMax >= rightMax && leftMax >= middleMax) {
                return leftMax;
            } else if (rightMax >= leftMax && rightMax > middleMax) {
                return rightMax;
            } else {
                return middleMax;
            }
        }

    }

代码块2

    public int findMiddleMax(int[] arr, int left, int right, int middle) {
        int lmidMax = -1000;
        int rmidMax = -1000;
        int sum = 0;
        // 找到向左的最大前缀
        for (int i = middle; i >= left; i--) {
            sum += arr[i];
            if (sum > lmidMax) {
                lmidMax = sum;
            }
        }
        // 找到向右的最大后缀
        sum = 0;
        for (int i = middle + 1; i <= right; i++) {
            sum += arr[i];
            if (sum > rmidMax) {
                rmidMax = sum;
            }
        }
        return lmidMax + rmidMax;
    }
  1. 将数组一分为二,那么该数组最大的子数组只可能有三种情况,位于左边,位于右边,位于中间(部分左边,部分右边)
  2. 那么就只要比较左边最大L1,右边最大R1,中间最大M1,得出的结果即是整个数组的最大子数组
  3. 在求左边最大L1(右边最大R1)的时候又回到了第一点,可再将左边(右边)的数组进行拆分再求对应左边最大L2(右边最大R2),依次递归最终left=right
  4. 横跨中间的最大值又是另一种求法,从middle—>leftmiddle—>right分别求最大,连起来即是最大,详见代码块2
    该算法的时间复杂度为O(N*LogN),个人理解:(二分法复杂度LogN)*(middle求最大值的N)
    该方法没想到怎么求解出对应最大子数组的下标,有会的童鞋指导下。

贪心算法

    @Override
    public void execute() {
        /**
         * sumStart ,sumEnd 分别表示累加数组的start位和end位
         * 也相当于 sumStart 是前一次计算结果,sumEnd 是后一次计算结果
         * 这里代码中潜在的关系是
         * sumEnd = sumStart + arr[end]
         */
        int sumStart = 0;
        int sumEnd = 0;
        int minStart = 0;// 表示最小的 minStart
        for (int endIndex = 0; endIndex < arr.length; endIndex++) {
             // 相当于构建累加数组 sumEnd 的过程,sumEnd[0..end] = sumEnd[0..end-1] + arr[end]
            sumEnd = sumEnd + arr[endIndex];
            // 寻找 minStart,minStart[j] = min(minStart[j-1],sum[0..j])
            if (sumStart < minStart) {
                minStart = sumStart;
                /**
                 * 如果此时sum[0..(endIndex - 1)](即sumStart) < minStart
                 * 则最大子数组的start只可能从endIndex开始
                 * 通俗一点就是endIndex(0~endIndex-1)之前的都已经被赋给minStart了
                 */
                this.start = endIndex;
            }
            // 更新 sumTotal
            if (sumEnd - minStart > maxSum) {
                maxSum = sumEnd - minStart;
                /**
                 * 如果maxSum值变动,说明最大子数组的end值为当前endIndex
                 */
                this.end = endIndex;
            }
            sumStart = sumStart + arr[endIndex];
        }
    }

在这里插入图片描述

  1. 因为是连续子数组,所以对于一个数组一定会存在end和start满足图片中的公式
  2. 所以最终演化成求解minStart和maxSum的两个,即是代码块中的两个判断的目的

该算法也是目前了解到的最优解,核心思想就是将用到了上一次循环的结果,不必每次都重新来过。代码中的注释已经很详细了,可以仔细体会。
只遍历了一次整个数组,时间复杂度O(N)

运行结果

最终运行的结果如下
在这里插入图片描述

标签:endIndex,arr,下标,int,起始,minStart,算法,数组,sumEnd
来源: https://blog.csdn.net/red_sheeps/article/details/102767426

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有