和最大的连续子数组

 求一个数组中,和最大的子数组,是一个比较经典的题目,《算法导论》中有一个用分治的经典解法,时间复杂度可以做到O(nlgn).

《编程之美》,《编程玑珠》中都有讨论这个问题,其中一个动态规划的做法非常精巧,思路很赞,时间复杂度也优化到了O(n).

  上面提到的两种解法对分治法,动态规划很有启发性,是非常好的练习题目。

(1)分治法:

  关键的思想在于把大题目转化为对多个小题目的求解。

  考虑,如果我们把数组arr[],分成大小相等的两半:arr_a与arr_b,那么当前数组的最大子和sub, 只有三种可能:

    1)sub 完全在 arr_a中。

    2)sub 完全在arr_b中。

    3)sub 横跨arr_a与arr_b.

  对于1), 2)这显然是当前问题的子问题,可以递归的求解。

  需要关注的是3).

  对于3),可以转换为这样的问题:

    假设中间点为mid,求一个包含arr[mid]的和最大的子数组。

    这个问题又等价于:求一个以arr[mid]为是后一个元素的和最大的子数组,及求一个以arr[mid]为开头的和最大的子数组。

    这两个问题显然可以直接通过累加来暴力解出的。

  

struct tuple
{

  int s;
  int e;
  int sum;

};

tuple MaxSub(int arr[], int low,int high)
{

  if(low < high)
   {
       int mid = (low + high)/2;
       tuple left    = MaxSub(arr,low,mid);
       tuple right  = MaxSub(arr,mid+1,high);
 
       tuple middle = MaxMiddle(arr,low,high);
       
       return max(left,right,middle);   
   }

 return {low,low,arr[low]};
}

  至于MaxMiddle,我们直接暴力计算就可以了。

tuple MaxMiddle(int arr,int low,int high)
{

     int mid = (low + high)/2;

     int max_left = 0, li = mid, tmp_sum = 0;
     for(int i = mid; i >= 0; i--)
      {
           tmp_sum += arr[i];
           if(tmp_sum > max_left)
            {
                    max_left = tmp_sum;
                    li = i;      
            }
      }

      int max_right = 0, ri = mid; 
      tmp_sum = 0;
     
      for(int i = mid; i < high, ++i)
       {
              tmp_sum += arr[i];
              if(tmp_sum > max_right)
               {
                     max_right = tmp_sum;
                     ri = i;
               }
       }

         return {li,ri, max_left+max_right - arr[mid]};
  
}

  这里用分治法的好处是思路非常清晰,代码也比较容易写,且时间复杂度也控制的很好。

  但这个题目还有更好的更优的解法,如:动态规划。

  

(2) 动态规划:

    这个解法的思路与分治法有些神的相似。

    如下考虑:

       对于数组arr[0]...arr[n].

       假设我们已经求出了子数组:arr[1] ... arr[n]的最大的子数组 arr[i] .... arr[j].及以arr[1]的开头的最大的子数组:arr[1]....arr[e].

       那么arr[0]...arr[n]的最大的子数组必然为以下三种情况之一:

        1)arr[0], 

        2)arr[0] + arr[1] + ... + arr[e].

        3)arr[i]+...+arr[j].

     所以问题就分解为怎样求子数组arr[1]....arr[n]的最大子数组和,及最大以arr[1]开关的子数组和。

      

     对于arr[n],显然,它的最大子数组为arr[n],最大以arr[n]开头的子数组,也是arr[n].

     假设我们已经求得到了当arr[k]....arr[n]的最大子数组为:arr[ki]....arr[kj].

     最大以arr[k]开头的子数组为:arr[k]....arr[ke].

    则对于arr[k-1]....arr[n]. 它的最大的以arr[k-1]开头的子数组只有两种情况:

     1)arr[k-1].

     2)arr[k - 1] + arr[k] +...+ arr[ke].

   而arr[-1]...arr[n]的最大子数组就只有两种情况:

    1)arr[k-1]开头的最大子数组,就是上面所求的结果。

    2) arr[k]...arr[n]的最大子数组。

void sovle(int arr[], int sz)
{

  int max[sz];
  int max2[sz];

  max[sz - 1] = arr[sz-1];
  max2[sz-1] = arr[sz-1];

  for(int i = sz - 2; i >= 0; --i)
   {
        if(max2[i+1) >= 0)
           max2[i] = arr[i]+max2[i+1];
        else
          max2[i] = arr[i];

        if(max2[i] > max[i+1])
          max[i] = max[2];
        else
          max[i] = max[i+1];
   }

   cout << max[0]  << endl;
}

 动态规划的做法非常巧妙,而且时间复杂度也优化到了O(n).

上面的伪代码中,空间复杂度为O(n),事实上还可以优化为到O(1),这里就不介绍了。

    

 (3)

 对于这个题目,我还在学校的时候第一次碰到,当时并没有像上面那些书中提到的一样想得深入和复杂,但也找到一个比较山寨的解法的,时间复杂度能做到O(n).

 题目事实上并没有很难,我们只要有一个变量记录一下当前累加的和,以及一个到目前为止,发现的最大的和,扫完一遍数组,就可以得到答案了。

  1)假设arr[0]为要找的子数组的第一个元素,sum = arr[0], temp_sum = arr[0];

     s = e = ts = te = 0;   (注:s == start,e == end, ts == temp start, te == temp end)

  2) temp_sum += arr[i]; i = 1,2,3,....

  3)

     a)如果 temp_sum > sum, s = ts,e=te.

     b)如果 temp_sum < 0,  ts = te = i + 1; temp_sum = 0;

      这里把ts 挪到i 之后是正确的,因为这里temp_sum < 0,说明之前的数组加起来都是负的,已经没有必要再去重新累加了。

     

#include <iostream>
using namespace std;


void solve(int arr[] ,int sz)
{
   int max, mi,mj;
   int i,m;
   
   max = arr[0];
   mi=mj=0;
   m = i = 0;
   
   for(int s = 0;s < sz ; ++s)
   {
      m+=arr[s];
      
      if(m > max)
      {
         max = m;
         mi = i;
         mj = s;
      }
      
      if(m < 0)
      {
        i = s + 1;
        m = 0;
      }
   }
   
   if( mi < sz)
     cout << "max sub-array: from " << mi << " to " << mj << ", sum = " << max << endl;
}


int main()
{
  int *arrs[] = {
   {1,2,3,4,5},
   {-2,2,3,-4,3},
   { -100, 100, -1, 100, -100}
   };
   
  return;
}
原文地址:https://www.cnblogs.com/catch/p/2970746.html