zoukankan      html  css  js  c++  java
  • 最大连续子数组和

    题目描述

    给定一个数组a[0,...,n-1],求其最大连续子数组(长度>=1)和


    输入描述

    第一行一个整数n(1<=n<=100000),然后依次输入n个整数(每个整数范围[-5000, 5000])


    输出描述

    输出一个整数表示最大子数组和


    样例输入

    5
    1 -1 1 1 -1


    样例输出

    2




    1.

    O(n)的算法可以用DP。

    设sum[i]为以第i个元素结尾且和最大的连续子数组。
    假设对于元素i,所有以它前面的元素结尾的子数组的长度都已经求得,那么以第i个元素结尾且和最大的连续子数组实际上,要么是以第i-1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i个元素,即sum[i] = max(sum[i-1] + a[i], a[i])。
    可以通过判断sum[i-1] + a[i]是否大于a[i]来做选择,而这实际上等价于判断sum[i-1]是否大于0。
    由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小。

    代码如下:
     1 #include <iostream>
     2 using namespace std;
     3 
     4 int main()
     5 {
     6     int n, num;
     7     long sum, max;
     8     cin >> n;
     9     cin >> sum;
    10     max = sum;
    11     for (int i = 1; i < n; i++)
    12     {
    13         cin >> num;
    14         if (sum > 0)
    15             sum += num;
    16         else
    17             sum = num;
    18         if (sum > max)
    19             max = sum;
    20     }
    21 
    22     cout << max;
    23 
    24     return 0;
    25 }

    Reference:http://www.cnblogs.com/waytofall/archive/2012/04/10/2439820.html

    2.

    O(nlogn) 的算法可以用分治解决。

    求一个数组a[low,high]的最大子数组,只有3种情况:
    * 该子数组位于a[low,high]的左边,即位于a[low,mid]中,则可以递归的求解a[low,mid].
    * 该子数组位于a[low,high]的右边,即位于a[mid+1,high]中,则可以递归的求解a[mid+1,high].
    * 该子数组位于a[low,high]的中间,即包括了a[mid],则直接从中间求解中间向左右延伸的最大子数组和,详见getMiddle函数。
    * 返回上面三种情况的最大值,则是我们要求解的答案。
     
    对于每一个子问题,即需要求的当前区间的最大连续子数组和,都逃不过以上三种可能性,而当前子问题的最优解,又构成以该子问题为基础的规模更大的子问题的一部分,所以向上回溯也可得到全局最优解,算法正确性得证。
     
    代码如下:
     1 class Solution:
     2     def getMidSum(self, lo, hi, mid):
     3         leftMax = float("-inf")
     4         rightMax = float("-inf")
     5         sum, i = 0, mid - 1
     6         while i > lo - 1:
     7             sum += self.nums[i]
     8             if sum > leftMax:
     9                 leftMax = sum
    10             i -= 1
    11         sum, i = 0, mid
    12         while i < hi:
    13             sum += self.nums[i]
    14             if sum > rightMax:
    15                 rightMax = sum
    16             i += 1
    17         return leftMax + rightMax
    18 
    19     def divConq(self, lo, hi):
    20         if hi - lo < 2:
    21             return self.nums[lo]
    22         else:
    23             mid = (lo + hi) >> 1
    24             leftSum = self.divConq(lo, mid)
    25             rightSum = self.divConq(mid, hi)
    26             midSum = self.getMidSum(lo, hi, mid)
    27             return max(leftSum, rightSum, midSum)
    28 
    29     # @param {integer[]} nums
    30     # @return {integer}
    31     def maxSubArray(self, nums):
    32         self.nums = nums
    33         return self.divConq(0, len(nums))

    在求 getMidSum 的时候,有两点需要注意:

    1. 从 mid 向两边求和,并不能简单的以 “当这个元素为正的时候就加进去,而遇到负数的时候就退出循环” 这样的策略,因为显然即便是包括mid元素在内的一段元素全部为正数的区间,也不并不一定是包含mid元素在内的最大连续和子区间(即便不要求包含 mid 也是如此)。倒是可以用 DP 的策略,但如果这样也没必要用 分治 了。对应的反例是 [1,2,-1,-2,2,1,-2,1,4,-5,4]。

    2. 需考虑当区间长度小于等于 2 的情况不要留下 bug。因为 6-10行 以及 12-16行代码 的循环是必须要进入一次的,这样才能保证 leftMax 和 rightMax 被更新而不至于在已经需要函数返回的时候还保留是无穷大的值。也就是说,这两个循环所涉及的区间,至少要保证其中有元素。因为我所有的区间定义都是“左闭右开”式的,也就是 [lo, hi) 式的。所以当区间长度为 2 时,假设是 [0, 2),那么 mid 为 1 (也就是mid值对于偶数长度区间其实是“右偏”的),所以我特意把对于 mid 的求和划到了右区间来做,这样保证第二个循环至少能进入一次。如果这里不这样做,则可能致使 rightMax 依然保持着负无穷的值,那么整个函数都会返回负无穷,这是不可取的。对应的反例是 [1,2]。

  • 相关阅读:
    Java学习9
    Windows环境下实现WireShark抓取HTTPS
    WireShark新手使用教程
    charles使用教程
    charles
    知道做到
    Appium 自动化测试改造思路
    今日总结
    今日总结
    今日总结
  • 原文地址:https://www.cnblogs.com/maples7/p/4471929.html
Copyright © 2011-2022 走看看