目录
6、优化的动态规划(O(n))
一、背景介绍
所谓最大子数组问题(maximum subarray problem),指的是给定一个数组Arr,计算Arr的非空连续子数组最大值。比如,数组 Arr = {133, 121, -12, 14, 534, -23, 87, -87, 81, 41},最大子数组应为[13 121 -12 14 34 -23 87 -87 81 41], 其和为269。
对于一组数简单的分为三种情况,如果Arr中的元素全部为正,则最大子数组就是它本身;如果Arr中的元素全部为负,则最大子数组就是第一个元素组成的数组;另外Arr中的元素既有正数,又有负数,则该如何求解呢?本文将介绍该问题的几种算法,并以不断优化的方式分析其时间复杂度。
二、蛮力枚举法(O(n^3))
以13开头的子数组有:1313 12113 121 -1213 121 -12 1413 121 -12 14 3413 121 -12 14 34 -2313 121 -12 14 34 -23 8713 121 -12 14 34 -23 87 -8713 121 -12 14 34 -23 87 -87 8113 121 -12 14 34 -23 87 -87 81 41以121开头的子数组有:121121 -12121 -12 14121 -12 14 34121 -12 14 34 -23121 -12 14 34 -23 87121 -12 14 34 -23 87 -87121 -12 14 34 -23 87 -87 81121 -12 14 34 -23 87 -87 81 41//忽略部分示例以41开头的子数组有:子数组有:41
/*** 最大子数组问题算法(蛮力法 O(n^3))*/private static void getSumOfSubArray01(int array[]) {int n = array.length;int cuSum, maxSum = Integer.MIN_VALUE, k, i, j;for (i = 0; i < n; i++) {for (j = i; j < n; j++) {cuSum = 0;System.out.print("子数组:");//k j分别为子数组的起止下标for (k = i; k <= j; k++) {cuSum = cuSum + array[k];//枚举所有的子数组System.out.print(array[k] + " ");}System.out.println();if (cuSum > maxSum) {maxSum = cuSum;}}}System.out.println("最大子数组之和为:" + maxSum);}
蛮力枚举实现思想非常的简单,就算是没有受过任何算法训练的程序员来说,根据题意应该也都能实现,但是蛮力枚举时间复杂度实在是太高,性能一般,接下来给出一种优化方案。
三、优化的蛮力枚举法(O(n^2))
/*** 最大子数组问题算法(优化的蛮力法)(重复利用已经计算的子数组和,相比较方法一,时间复杂度为:O(n^2))*/private static void getSumOfSubArray02(int array[]) {int n = array.length;int cuSum, maxSum = Integer.MIN_VALUE, i, j;for (i = 0; i < n; i++) {for (j = i; j < n; j++) {cuSum = 0;cuSum = cuSum + array[j];if (cuSum > maxSum) {maxSum = cuSum;}}}System.out.println("最大子数组之和为:" + maxSum);}
四、分而治之算法(O(nlogn))



/*** @Date: 2020-03-24* @Description: 最大子数组问题算法(分治法 、 无左右下标)*/public static int maxSubArraySort(int a[], int low, int high) {if (low > high) {return 0;}if (low == high) {return a[0];}int S_Left = 0, S_Right = 0, S_Cross_Left_Right = 0;//while (low < high) {int i = low;int j = high;int mid = (i + j) / 2;S_Left = maxSubArraySort(a, i, mid);S_Right = maxSubArraySort(a, mid + 1, j);S_Cross_Left_Right = CrossLeftAndRight(a, i, mid, j);//}return maxInThreeNum(S_Left, S_Right, S_Cross_Left_Right);}public static int CrossLeftAndRight(int a[], int low, int mid, int high) {int Sub_Left_Sum = Integer.MIN_VALUE;int Sub_Left_Sum_Tem = 0;for (int i = mid; i >= low; i--) {Sub_Left_Sum_Tem += a[i];if (Sub_Left_Sum_Tem > Sub_Left_Sum) {Sub_Left_Sum = Sub_Left_Sum_Tem;}}int Sub_Right_Sum = Integer.MIN_VALUE;int Sub_Right_Sum_Tem = 0;for (int i = mid + 1; i <= high; i++) {Sub_Right_Sum_Tem += a[i];if (Sub_Right_Sum_Tem > Sub_Right_Sum) {Sub_Right_Sum = Sub_Right_Sum_Tem;}}return Sub_Left_Sum + Sub_Right_Sum;}public static int maxInThreeNum(int S_Left, int S_Right, int S_Cross_Left_Right) {return (S_Left > S_Right ? (S_Left > S_Cross_Left_Right ? S_Left : S_Cross_Left_Right) : (S_Right > S_Cross_Left_Right ?S_Right : S_Cross_Left_Right));}
对于动规算法来讲,基本概念介绍限于篇幅原因这里不再赘述,如果初次接触也没关系,这里会介绍其算法分析特点,抓住了关键点就不难理解。
适用于动态规划的问题一般都满足以下条件:①、大多是决策性求解最优解问题。②、大问题的最优解依赖于小问题的最优解(最优子结构性质)。三、子问题最优解中存在重叠性(重叠子问题)。
然后介绍一般性的动态规划通常包括四步骤:
分析原问题(最优子结构和重叠子问题)
找出递推关系式
自底向上计算
追踪最优过程
对于最大子数组之和问题首先求解的是最大子数组,一个数组的子数组会有很多个,但是最大子数组之和只有一个(最大子数组可能存在多个),满足①。
对于本文章给出的数组例子,元素一共有十个,求解十个元素的最大子数组之和一定依赖于求解九个元素最大子数组之和、八个元素最大子数组之和......因为如果我们连九个元素最大子数组之和、八个元素最大子数组......之和都不知道,那么根本无从谈起求解十个元素最大子数组之和。满足②。
基于上面所述,大问题的求解依赖于小规模问题的求解,换言之,比如我们求解三个元素的最大子数组之和和求解四个元素的最大子数组之和一定存在重叠部分,也就是说三个元素的最大子数组之在求解四个元素的最大子数组之和的过程中一定会出现。满足③。
如果利用动态规划解本题该方法时间复杂度为:o(n),但是额外使用了两个数组空间,其空间复杂度为:o(n)。
private static void getSumOfSubArray03(int array[]) {int n = array.length;int ev[] = new int[n];int curr[] = new int[n];int rec[] = new int[n];//初始化ev[0] = curr[0] = array[0];ev[n - 1] = curr[n - 1] = array[n - 1];for (int i = 1; i < n; i++) {//Ent[i]是数组array[i]前i个数最大子数组之和ev[i] = max(ev[i - 1] + array[i], array[i]);if (ev[i - 1] > 0) {rec[i] = rec[i - 1];} else if (ev[i - 1] <= 0) {rec[i] = i;}curr[i] = max(ev[i], curr[i - 1]);}System.out.println("最大子数组之和为:" + curr[n - 1]);getRec(rec, ev);}private static void getRec(int rec[], int end[]) {int S = end[end.length - 1];int l = 0, r = 0;for (int i = end.length - 1; i > 0; i--) {if (S < end[i]) {S = end[i];r = i;l = rec[i];}}System.out.println("起止下标为:" + l + "-" + r);}
这种方法是为了贴近动态规划解法的一般步骤按部就班进行实现。
ev[i]是数组array中以array[i]为结尾的最大子数组之和。
curr[n-1]表示为:array[0]...array[n-1]的最大子数组之和。
rec用来记录下标,getRec方法用于追踪最优解的起止下标。
private static void getSumOfSubArray05(int array[]) {int n = array.length;int ev[] = new int[n];//初始化ev[0] = array[0];ev[n - 1] = array[n - 1];for (int i = 1; i < n; i++) {ev[i] = max(ev[i - 1] + array[i], array[i]);}System.out.println("最大子数组之和为:" + Arrays.stream(ev).max().getAsInt());}
private static void getSumOfSubArray04(int array[]) {int n = array.length;int ev = array[0];int curr = array[0];for (int i = 1; i < n; i++) {ev = max(ev + array[i], array[i]);curr = max(ev, curr);}System.out.println("最大子数组之和为:" + curr);}




