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

     求一个数组中,和最大的子数组,是一个比较经典的题目,《算法导论》中有一个用分治的经典解法,时间复杂度可以做到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;
    }
  • 相关阅读:
    mysql中的内连接and 多表查询
    webdriver中的三大等待及窗口的切换
    postman断言
    postman数据驱动ddt
    postman环境变量和全局变量的使用
    postman 接口请求过程
    垃圾脑瘫的坑
    待填的坑
    CF185D
    CF235E 题解(转)
  • 原文地址:https://www.cnblogs.com/catch/p/2970746.html
Copyright © 2011-2022 走看看