有这么一个题目:
求某个数组中连续的最大和的子数组,例如输入为:{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纳秒 * 相差了两个数量级...... */ }