分治策略之求连续最大和子数组

有这么一个题目:

求某个数组中连续的最大和的子数组,例如输入为:{1, -1, -4, 3, 1, 4, 5, -10, 20},那么要求输出为:{3, 1, 4, 5, -10, 20}。

首先我们使用最暴力的手段来解决,即双重循环遍历:

    public static void baoliManage(int[] nums)
    {
        int max = 0, left = 0, right = 0;
        for (int i = 0; i < nums.length; i++)
        {
            int sum = 0;
            for (int j = i; j < nums.length; j++)
            {
                sum += nums[j];
                if (sum > max)
                {
                    max = sum;
                    left = i;
                    right = j;
                }
            }
        }
        System.out.println(left + " " + right);
    }

 

这种方式可以达到想要的结果,是没有问题的,那么如何使用分治的方式呢?

分治的思想即递归加归并,简单而言就是将大问题划分为多个小问题,然后在将结果归并在一起,根据这个思想如何将上述问题进行同治呢?

思考下会出现的结果,会发现有三种结果:出现在左侧、出现在右侧,跨中间节点。

作为的左侧,即:left~middle,所谓的右侧即:middle+1 ~ right,所谓的跨中间节点即:left ~ right,其中left < middle,right > middle。

根据这种思想,我们来对递归后的结果进行分析:

1、每次递归都会产生3个结果,其中跨中间节点可直接计算;

2、递归后再进行左侧或右侧递归时,最后会递归到只有一个元素;

 

根据上述的思想,我们首先来实现一下跨中间节点如何处理:

public static int[] crossManage(int[] nums, int left, int mid, int right)
    {
        int sumLeft = 0, sum = 0, indexLeft = 0;
        for (int i = mid; i >= left; i--) // 求左侧最大值
        {
            sum += nums[i];
            if (sum > sumLeft)
            {
                sumLeft = sum;
                indexLeft = i;
            }
        }
        sum = 0;
        int sumRight = 0, indexRight = 0;
        for (int i = mid; i <= right; i++)
        {
            sum += nums[i];
            if (sum > sumRight)
            {
                sumRight = sum;
                indexRight = i;
            }
        }
        return new int[]{indexLeft, indexRight, sumLeft + sumRight};
    }

我们可以看到其时间复杂度为o(n),是非常低的。

然后我们看一下最后的整体实现:

    public static int[] minMaxManage(int[] nums, int left, int right)
    {
        if (left == right)
        {
            return new int[]{left, right, nums[left]};
        }
        else
        {
            int mid = (left + right)/2;
            int[] crossArray = crossManage(nums, left, mid, right);
            int[] leftArray = minMaxManage(nums, left, mid);
            int[] rightArray = minMaxManage(nums, mid + 1, right);
            if (crossArray[2] > leftArray[2] && crossArray[2] > rightArray[2])
            {
                return crossArray;
            }
            if (leftArray[2] > crossArray[2] && leftArray[2] > rightArray[2])
            {
                return leftArray;
            }
            else
            {
                return rightArray;
            }
        }
    }

从上述的实现中可以看出,每次的处理都是分为三部分,其中两部分是使用递归,还有一部分是直接实现。

下面是一个测试用例,测试一下时间复杂度:

    public static void main(String[] args)
    {
//        int[] nums = {-1, 10, -2, 3, 5, 6, 20, 11, -20, 21, 1, -22};
        int[] nums = new int[100000];
        for (int i = 0; i < nums.length; i++)
        {
            double m = Math.random();
            double n = Math.random();
            if (n > 0.5d)
            {
                nums[i] = (int)(-100000 * m);
            }
            else
            {
                nums[i] = (int)(100000 * m);
            }
        }
        long start = System.nanoTime();
        int[] subArray = minMaxManage(nums, 0, nums.length - 1);
        long end = System.nanoTime();
        System.out.println(subArray[0] + " " + subArray[1]);
        baoliManage(nums);
        long end2 = System.nanoTime();
        System.out.println("同治算法结果:" + (end - start) + "纳秒");
        System.out.println("暴力算法结果:" + (end2 - end) + "纳秒");
        
        /**
         * 同治算法结果:49939222纳秒
         * 暴力算法结果:2574574866纳秒
         * 相差了两个数量级......
         */
    }

 

转载于:https://www.cnblogs.com/bruceshao/articles/7112464.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值