zoukankan      html  css  js  c++  java
  • 《编程之美》解题报告:2.14 求数组的子数组之和的最大值

    引言

       

    本文用了五种方法来求解这道题,从最初的n^3的复杂度到后面的n的复杂度,分别用到了递推公式、分而治之以及动态规划的方法去一步步降低算法的复杂度,《编程之美》书中关于分而治之的代码并没有提供,本文中将其补全,动态规划的代码与书中有所出入,个人感觉这样更好理解一些。

       

    解题报告

       

    首先我们很容易想到的一个解法就是三层遍历,首先子数组必定是连续的一串值,相当于从原数组array的下标范围0~n-1中选出ij,去算arra[i]~array[j]的和,于是我们可以得到最初的第一个解法

       

    public static int sol1(int[] array) {

    int lenght = array.length;

    int result = Integer.MIN_VALUE;

    int tempSum = 0;

    for (int i = 0; i < lenght; i++) {

    for (int j = i; j < lenght; j++) {

    tempSum = 0;

    for (int k = i; k <= j; k++) {

    tempSum += array[k];

    }

    if (tempSum > result) {

    result = tempSum;

    }

    }

    }

    return result;

    }

       

    接下来,由于array[i]~array[j]的和可以由array[i]~array[j-1]的和再与array[j]相加得到,所以我们可以将上面的代码减少一层循环,从而将复杂度降低到n^2

       

    public static int sol2(int[] array) {

    int lenght = array.length;

    int result = Integer.MIN_VALUE;

    for (int i = 0; i < lenght; i++) {

    int tempSum = 0;

    for (int j = i; j < lenght; j++) {

    tempSum += array[j];

    if (tempSum > result) {

    result = tempSum;

    }

    }

    }

    return result;

    }

       

    现在已经降低到n^2了,但是我们并没有用到分而治之的方法,一般这种二分法可以将复杂度再一次降低到nlogn,那么如何去使用呢。

       

    考虑将所给数组array(array[0]~array[n-1])分为长度相等的两段数组(array[0],...,array[n/2-1])(array[n/2],...,array[n-1])

       

    然后分别求出这两段数组各自最大子段和,得到 MaxLeftBorderSum和MaxRightBorderSum

       

    则原数组(array[0],...,array[n-1])的最大子段和分为以下三种情况:

       

    a.(array[0],...,array[n-1])的最大子段和与(array[0],...,array[n/2-1])的最大子段和相同;

    b.(array[0],...,array[n-1])的最大子段和与(array[n/2],...,array[n-1])的最大子段和相同;

    c.(array[0],...,array[n-1])的最大子段跨过其中间两个元素array[n/2-1]array[n/2].

       

    对应ab两个问题是规模减半的两个相同的子问题,可以用递归求得。

    对于c,需要找到以array[n/2-1]结尾的和最大的一段数组和S1=(array[i],...,array[n/2-1])和以array[n/2]开始和最大的一段和S2=(array[n/2],...,array[j]),那么第三种情况的最大值为S1+S2

       

    public static int sol3(int[] array, int left, int right) {

    if (left == right) {// 只有一个元素

    if (array[left] > 0)

    return array[left];

    else

    return 0;

    }

    int start = left;

    int mid = (left + right) / 2;

    int end = right;

    int MaxLeftBorderSum, MaxRightBorderSum; // 从中间分别到左右两侧的最大连续子序列值,对应c

    int LeftBorderSum, RightBorderSum;

    int MaxLeftSum = sol3(array, start, mid);

    int MaxRightSum = sol3(array, mid + 1, end);

    MaxLeftBorderSum = 0;

    LeftBorderSum = 0;

    for (int i = mid; i >= left; i--) {

    LeftBorderSum += array[i];

    if (LeftBorderSum > MaxLeftBorderSum)

    MaxLeftBorderSum = LeftBorderSum;

    }

    MaxRightBorderSum = 0;

    RightBorderSum = 0;

    for (int i = mid + 1; i <= right; i++) {

    RightBorderSum += array[i];

    if (RightBorderSum > MaxRightBorderSum)

    MaxRightBorderSum = RightBorderSum;

    }

    int max1 = MaxLeftSum > MaxRightSum ? MaxLeftSum : MaxRightSum;

    int max2 = MaxLeftBorderSum + MaxRightBorderSum;

    return max1 > max2 ? max1 : max2;

    }

       

    以上已经利用分而治之的方法将复杂度从n^2降低到了nlogn,考虑到还有一种降低复杂度的方法那就是动态规划,所以我们尝试用动态规划的思想去进一步降低复杂度

       

    动态规划的思想是将一个大问题(N个元素数组)转换为一个较小的问题(n-1个元素数组)。

       

    我们假设result[0]为已经找到数组[0,1,...n-1]中子数组和最大的,即保存当前找到的最大子数组。sum[i]为包含第i个元素且和最大的连续子数组。对于数组中的第i+1个元素有两种选择:

       

    a.作为新子数组的第一个元素

    b.放入前面已经找到的最大的子数组sum[i-1]中。

       

    public static int sol4(int[] array) {

    int n = array.length;

    int[] sum = new int[n];

    int[] result = new int[n];

    sum[0] = array[0];

    result[0] = array[0];

    for (int i = 1; i < n; i++) {

    sum[i] = max(array[i], array[i] + sum[i - 1]);

    // A[i]>A[i]+sum[i-1],则作为新子数组的第一个元素

    // 否则放入前面已经找到的最大的子数组sum[i-1]

    result[i] = max(sum[i], result[i - 1]);

    }

    return result[n - 1];

    }

       

    以上的代码其实sum数组和result数组可以只用一个变量就可以了,不用真的用到一个数组,那么将这一点简化,可以得到简化版的最终方法,复杂度为n

       

    static int sol5(int[] array, int n) {

    int sum = array[0];

    int result = array[0];

    for (int i = 1; i < n; i++) {

    sum = max(array[i], array[i] + sum);

    result = max(sum, result);

    }

    return result;

    }

  • 相关阅读:
    二维卷积层
    gluon 实现多层感知机MLP分类FashionMNIST
    gluon实现softmax分类FashionMNIST
    gluon 实现线性回归
    GPU使用
    Python迭代器和生成器
    L2范数惩罚项,高维线性回归
    多项式拟合
    模型选择,欠拟合,过拟合
    多层感知机MLP的gluon版分类minist
  • 原文地址:https://www.cnblogs.com/keedor/p/4516383.html
Copyright © 2011-2022 走看看