最大和子数组

最大和子数组问题

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
13 -3 -25 20 -3 -16 -23 18 20 -7 12 -5 -22 15 -4 7

求这个数组中子数组的最大和。

分治法的思想:

我们来思考如何用分治法来求解最大子数组问题。假定我们要寻找子数组A[low,...,high]的最大子数组。使用分治技术,意味着我们要将子数组划分为两个规模尽量相等的子数组。也就是说,找到子数组的中央位置,比如mid,然后考虑求解两个子数组A[low,...,mid]和A[mid+1,..,high]。A[low,...,high]的任何连续子数组A[i,...,j]所处的位置必然是一下三种情况之一:

  • 完全位于子数组A[low,...,mid]中,因此low≤i≤j≤mid。
  • 完全位于子数组A[mid+1,..., high]中,一次mid<i≤j≤high。
  • 跨越了中点,因此low≤i≤mid<j≤high。
    因此,A[low,...,high]的一个最大子数组所处的位置必然是这三种情况之一。实际上A[low,...,high]的最大子数组必然是完全位于A[low,...,mid],完全位于A[low+1,...,high]中或者跨越中点的所有子数组中和最大者。我们可以递归求解A[low,...,high]和A[mid+1,..,high]的最大子数组,因为这两个子问题仍然是最大子数组问题,只是规模更小。因此,剩下的全部工作就是寻找跨越中点的最大子数组,然后再三种情况中选择最大者。

寻找跨越中点的最大子数组(O(N))伪代码:

  1. find_max_crossing_subarray(a,low,mid,hight)
  2. left_sum = -65535
  3. sum = 0
  4. for i = mid dwonto low
  5. sum = sum + A[i]
  6. if sum > left_sum
  7. left_sum = sum
  8. max_left = i
  9. right_sum = -65535
  10. sum = 0
  11. for j=mid+1 to high
  12. sum = sum +A[j]
  13. if sum>right_sum
  14. right_sum = sum
  15. max_right = j
  16. return(max_left,max_right,left_sum+right_sum)

有了一个线性时间的find_max_crossing_subarray,我们就可以设计求解最大子数组问题的分治算法的伪代码了:

  1. find_max_sumarray(A,low,high)
  2. if high==low
  3. return(low,high,A[low]) //base case:only one element
  4. else mid = (low+high)/2
  5. (left_low,left_high,left_sum)= find_max_sumarray(A,low,mid)
  6. (cross_low,cross_high,right_sum) = find_max_sumarray(A,mid+1,high)
  7. if left_sum >= right_sum and left_sum >= cross_sum
  8. return (left_low,left_high,left_sum)
  9. elseif right_sum >= left_sum and right_sum >= cross_sum
  10. return (right_low,right_hight,right_sum)
  11. else return (cross_low,cross_high,cross_sum)

分治算法的时间分析:

T(n)={Θ(1)2T(n/2)+Θ(n)ifn=1ifn>1

求解为T(n)=Θ(nlgn)。
我们利用分治法得到了一个渐进复杂性优于暴力求解方法的算法。通过归并排序和本次的最大子数组问题,我们开始对分支方法的强大能有了一些了解。有时,对某个问题,分支方法能给出最快的算法,而其他时候,我们(不用分治方法)甚至能做得更好。以此为例,不用分治法,而用动态规划的思想求解本题能得到线性时间复杂度的算法。

非递归的思考

使用如下思想为最大子数组问题设计一个非递归的,线性时间的算法。从数组的左边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组。若已知A[1,...,j]的最大子数组,基于如下性质将解扩展为A[1,..,j+1]的最大子数组:A[1,..,j+1]的最大子数组要么是A[1,..,j]的最大子数组,要么是某个子数组A[i,...,j+1](1<=i<=j+1)。在已知A[1,..,j]的最大子数组的情况下,可以在线性时间内找出形如A[i,...j+1]的最大子数组。
怎么找出来呢?

  1. 一个O(n)的算法:
    设F[j]为A[1..j]数组的最大和子数组。
    我只想到

    F[j]=max1≤i≤j{F[j?1],∑A[i,..,j]}

    如果直接这样编程,时间就不是线性的了。如果我们稍作修改呢?
    设Sum[0,0]=0;

    ∵∑A[i,...,j]=Sum[0,...,j]?Sum[0,...,i]?F[j]=max{F[j?1],Sum[0,..,j]?min0≤i≤jSum[0,..i]}

    我们可以先计算出min0≤i≤jSum(0,..i)来,
    设sum(i)=Sum[0,...i],sum(0)=0
    则:sum(i)=sum(i?1)+A[i],1≤i≤i,
    可以得到以下步骤:

    1. 求前缀数组和以及最小和前缀

      • 定义:前缀和sum[i] = A[1]+...+A[i],sum[0]=0。
        并且定义: m[i]是A[1,...,i]子数组中最小和的前缀串。m[0] = 0.
      • 则:sum[j]=sum[j?1]+A[j].
        顺带求出m[j]=min(m[j?1],sum[j])
    2. F[j]=max{F[j?1],sum[j]?m[j]}

      因为每一步都是线性的,所以总的时间复杂是O(n)。
      可以看到其实sum[j] - m[j]就是以A[j]结尾的子数组最大和。如果我们把这部分单独拿出来考虑,可以得到另一种方法:

  2. 另一个稍作改进的算法
    既然sum[j]-m[j]是以A[j]结尾的最大子数组和S[j],那么我们求出所有的S[1],...,S[n],然后求一个最大的,不就得到解了么?
    1. 求前缀数组和以及最小和前缀

      • 定义:前缀和sum[i] = A[1]+...+A[i],sum[0]=0。
        并且定义: m[i]是A[1,...,i]子数组中最小和的前缀串。m[0] = 0.
      • 则:sum[j]=sum[j?1]+A[j].
        顺带求出m[j]=min(m[j?1],sum[j])
        那么,S[j] = sum[j] - m[j]
    2. 求出S[i,...,N]的最大值

利用动态规划来解决

最优子结构:
设S[j]是以元素A[j]结尾的和最大子数组。那么已知j以前的状态,S[0],..,S[j-1]怎么求S[j]呢?
其实

S[j]=max{S[j?1]+A[j],A[j]}

重叠子问题:
如果直接这样递归的去做,肯定会重复计算很多次相同的子问题。
所以我们改成迭代,经过O(n)时间复杂度就可以求出答案。
*代码如下:

  1. /*求最大子数组的和,以及返回这个数组本身*/
  2. int MaxSubarray(const int * a, int size, int & from, int & to){
  3. if (!a || (size <= 0)){
  4. from = to = -1;
  5. return 0;
  6. }
  7. from = to = 0;
  8. int sum = a[0];
  9. int result = sum;
  10. int fromNew = 0; // 新的子数组起点
  11. for (int i = 0; i < size; i++){
  12. if (sum>0){
  13. sum += a[i];
  14. }
  15. else{
  16. sum = a[i];
  17. fromNew = i;
  18. }
  19. if (result < sum){
  20. result = sum;
  21. from = fromNew;
  22. to = i;
  23. }
  24. }
  25. return result;
  26. }

来自为知笔记(Wiz)

时间: 2025-01-13 03:56:25

最大和子数组的相关文章

二维最大和子数组

1.设计思路 因为计算了一位的最大和子数组,所以想办法将二维的数组转换成一维子数组进行求解: 首先,将二维数组的第一行(列)赋值到新的一维数组中,在一维的数组中求得子数组的最大值, 然后,将二维数组的第二行(列)加到一维数组中,再次求得子数组的最大值,并和第一步求得的最大值进行比较,若新的值比较大,留下最大值,如果新值比较小,退出这一个循环,从第二行(列)开始第一步,这样就可以求出最大和子数组: 2.源代码 #include<iostream> #include<ctime> us

数组中 最大和 的子数组

题目: 输入一个整型数组,数据元素有正数也有负数,求元素组合成连续子数组之和最大的子数组,要求时间复杂度为O(n). 例如: 输入的数组为1, -2, 3, 10, -4, 7, 2, -5,最大和的连续子数组为3, 10, -4, 7, 2,其最大和为18. 背景: 本题最初为2005年浙江大学计算机系考研题的最后一道程序设计题,在2006年里包括google在内的很多知名公司都把本题当作面试题. 由于本题在网络中广为流传,本题也顺利成为2006年程序员面试题中经典中的经典. 分析: 如果不考

找出一个整数数组的和最大的连续子数组

题目: 给任意一个整数数组,找出这个数组的和最大的连续子数组(子数组的和最大且子数组连续).要求:算法的时间复杂度为O(n). 程序设计思想: 1:用maxValue记录当前连续子数组和为最大的和的值,初始化其值为:maxValue=a[0].注:记数组为a[n]. 2:这个过程总的思想就是,从数组头开始往后,每次加进一个值,它们的和记为tempValue,若tempValue比新加进来的数值本身要小,应该从这个位置开始重新开始计算tempValue的值.而每次的tempValue都应该和max

【算法30】从数组中选择k组长度为m的子数组,要求其和最小

原题链接:codeforce 267 Div2 C 问题描述: 给定长度为n的数组a[],从中选择k个长度为m的子数组,要求和最大. 形式描述为:选择$k$个子数组[$l_1$, $r_1$], [$l_2$, $r_2$], ..., [$l_k$l1, $r_k$] (1 ≤ $l_1$ ≤$r_1$ ≤$l_2$ ≤ $r_2$ ≤... ≤$l_k$ ≤ $r_k$ ≤ n; $r_i-r_i+1$), 使得$\sum_{i=1}^{k}\sum_{j=l_i}^{r_i}p_j$ 问题

最长可整合子数组

可整合子数组:按由小到大排完序之后,后面的数比前一个数大1,如:[2,4,3,6,5]就是可整合数组. 1 // getLiL.cpp : 定义控制台应用程序的入口点. 2 // 3 4 #include "stdafx.h" 5 #include <iostream> 6 #include <hash_set> 7 #include <iterator> 8 #include <set> 9 10 using namespace std

求一个数组中和最小的连续子数组

#include<stdio.h> #define MAX_LENGTH 10 int main() { int a[MAX_LENGTH]={1,2,3,-2,4,-6,-8,5,3,1}; int i,j,beg,end,tmp,min=0x7fffffff; //beg和end分别为子数组中首末元素下标,min为无穷大的数 beg=end=tmp=0; for(i=0;i<MAX_LENGTH;++i) { tmp=a[i]; for(j=i+1;j<MAX_LENGTH;+

求二维数组中子数组和中最大的值,及子数组

求二维数组中子数组和中最大的值,及子数组 个人信息:就读于燕大本科软件工程专业 目前大三; 本人博客:google搜索"cqs_2012"即可; 个人爱好:酷爱数据结构和算法,希望将来从事算法工作为人民作出自己的贡献; 编程语言:C++ ; 编程坏境:Windows 7 专业版 x64; 编程工具:vs2008; 制图工具:office 2010 powerpoint; 硬件信息:7G-3 笔记本; 真言 每次着急写程序,碰到问题就头疼,头疼之后便是满满的收获,付出总有回报. 题目 求

[LeetCode] Shortest Unsorted Continuous Subarray 最短无序连续子数组

Given an integer array, you need to find one continuous subarray that if you only sort this subarray in ascending order, then the whole array will be sorted in ascending order, too. You need to find the shortest such subarray and output its length. E

用reduce装逼 之 多个数组中得出公共子数组,统计数组元素出现次数

昨天做了一道美团的面试题,要求是给N个数组,找出N个数组的公共子数组. var a = [7,2,3,4,5]; var b = [4,2,3,7,6]; var c = [2,3,3,3,7]; var d = [4,2,3,8,7]; 以上四个数组,有公共子数组2, 3,7 function main(){ var result = []; var arr = arguments[0]; for(var i=1 ; i<arguments.length ; i++){ var arr = a