【问题标题】:Maximum sum of all subarrays of size k for each k=1..n每个 k=1..n 的所有大小为 k 的子数组的最大总和
【发布时间】:2015-08-14 17:16:41
【问题描述】:

给定一个大小为 n 的数组,对于从 1 到 n 的每个 k,找到大小为 k 的连续子数组的最大和。

这个问题有一个明显的解决方案,时间复杂度 O(N2) 和 O(1) 空间。 Lua代码:

array = {7, 1, 3, 1, 4, 5, 1, 3, 6}
n = #array

function maxArray(k)
    ksum = 0
    for i = 1, k do
        ksum = ksum + array[i]
    end
    max_ksum = ksum
    for i = k + 1, n do
        add_index = i
        sub_index = i - k
        ksum = ksum + array[add_index] - array[sub_index]
        max_ksum = math.max(ksum, max_ksum)
    end
    return max_ksum
end

for k = 1, n do
    print(k, maxArray(k))
end

有没有时间复杂度更低的算法?例如,O(N log N) + 额外的内存。

相关主题:

【问题讨论】:

    标签: arrays algorithm data-structures dynamic-programming interval-tree


    【解决方案1】:

    一个有效的解决方案基于这样一个事实,即使用大小为 k 的前一个子数组(或窗口)的总和可以在 O(1) 时间内获得大小为 k 的子数组(或窗口)的总和。除了大小为 k 的第一个子数组,对于其他子数组,我们通过删除最后一个窗口的第一个元素并添加当前窗口的最后一个元素来计算和。

    这里是相同的实现

    int maxSum(int arr[], int n, int k) 
    { 
    // k must be greater 
    if (n < k) 
    { 
       cout << "Invalid"; 
       return -1; 
    } 
    
    // Compute sum of first window of size k 
    int res = 0; 
    for (int i=0; i<k; i++) 
       res += arr[i]; 
    
    // Compute sums of remaining windows by 
    // removing first element of previous 
    // window and adding last element of  
    // current window. 
    int curr_sum = res; 
    for (int i=k; i<n; i++) 
    { 
       curr_sum += arr[i] - arr[i-k]; 
       res = max(res, curr_sum); 
    } 
    
    return res; 
     } 
    

    时间复杂度:O(n) 辅助空间:O(1)

    Source

    【讨论】:

      【解决方案2】:
      int maxCrossingSum(int arr[], int l, int m, int h) 
      { 
          // Include elements on left of mid. 
          int sum = 0; 
          int left_sum = INT_MIN; 
          for (int i = m; i >= l; i--) 
          { 
              sum = sum + arr[i]; 
              if (sum > left_sum) 
                left_sum = sum; 
          } 
      
          // Include elements on right of mid 
          sum = 0; 
          int right_sum = INT_MIN; 
          for (int i = m+1; i <= h; i++) 
          { 
              sum = sum + arr[i]; 
              if (sum > right_sum) 
                right_sum = sum; 
          } 
      
          // Return sum of elements on left and right of mid 
          return left_sum + right_sum; 
      } 
      
      // Returns sum of maxium sum subarray in aa[l..h] 
      int maxSubArraySum(int arr[], int l, int h) 
      { 
         // Base Case: Only one element 
         if (l == h) 
           return arr[l]; 
      
         // Find middle point 
         int m = (l + h)/2; 
      
         /* Return maximum of following three possible cases 
            a) Maximum subarray sum in left half 
            b) Maximum subarray sum in right half 
            c) Maximum subarray sum such that the subarray crosses the midpoint */
         return max(maxSubArraySum(arr, l, m), 
                    maxSubArraySum(arr, m+1, h), 
                    maxCrossingSum(arr, l, m, h)); 
      } 
      

      解释

      使用分而治之的方法,我们可以在 O(nLogn) 时间内找到最大子数组和。以下是分治算法。

      1) 将给定数组分成两半

      2) 返回以下三个中的最大值

      ….a) 左半部分的最大子数组和(递归调用)

      ….b) 右半部分的最大子数组和(递归调用)


      source

      【讨论】:

        【解决方案3】:

        如果您不添加任何其他约束,我认为没有比 O(N²) 更有效的解决方案。换句话说,除了探索所有其他子数组之外,没有其他方法可以确定您找到了最大和子数组。

        因此,最复杂的解决方案包括 O(N²/2),它是给定长度 N 的数组的连续子数组的总数。

        我个人会使用动态编程方法来实现这一点。这个想法是有一个部分结果的楔形,并使用它们来构建子数组的当前总和(代替计算整个总和)。无论如何,这“只”给出了一个恒定的加速,因此复杂度是 O(N²/2)~O(N²)。

        以下是伪代码——不好意思没讲Lua

        // here we place temporary results, row by row alternating in 0 or 1
        int[2][N] sum_array_buffer
        // stores the start of the max subarray
        int[N] max_subarray_start
        // stores the value
        int[N] max_subarray_value
        
        array = {7, 1, 3, 1, 4, 5, 1, 3, 6}
        // we initialize the buffer with the array (ideally 1-length subarrays)
        sum_array_buffer[1] = array
        // the length of subarrays - we can also start from 1 if considered
        for k = 1 ; k <= (N); ++k:
            // the starting position fo the sub-array
            for j = 0; j < (N-k+1); ++j:
                sum_array_buffer[k%2][j] = sum_array_buffer[(k+1)%2][j] + array[j+k-1]
                if j == 0 || sum_array_buffer[k%2][j] > max_subarray_value[k]:
                    max_subarray_value = sum_array_buffer[k%2][j]
                    max_subarray_start[k] = j
        
        
        for k = 1 ; k <= (N); ++k:
            print(k, max_subarray_value[k])
        

        图形化:

        【讨论】:

          【解决方案4】:

          我们创建了一个容量为 k 的 Dequeue,Qi,它仅存储 k 个元素的当前窗口的有用元素。如果一个元素在当前窗口中并且大于当前窗口中它左侧的所有其他元素,则该元素很有用。我们将所有数组元素一一处理并维护 Qi 以包含当前窗口的有用元素,并且这些有用元素按排序顺序维护。当前窗口中Qi前面的元素最大,Qi后面的元素最小。

          【讨论】:

            【解决方案5】:

            问题可以归结为最小和抽搐,请参阅https://core.ac.uk/download/pdf/84869149.pdf 中的第 2.4 节(MCSP)。因此,目前您可以预期的最佳复杂度可能是 O(n^2/polylog(n))。

            【讨论】:

              【解决方案6】:
                  The above question can be solved by O(n).
                  Please try this algorithm.
                  lets say k=3.
              array = {7, 1, 3, 1, 4, 5, 1, 3, 6}
                  maxsum=0.
                  1)We start with adding 7+1+3 and store sum=11.since sum >maxsum.maxsum=11.
                  2)Now since size of k=3,next continuous array is 1+3+1.so how we get this sum??
                  remove 7 from sum and add 1 to sum.so now sum is 5.Check if sum>maxsum.
                  3)Similarly do for other elements as well.This loop will run until (n-1).``
              

              请在此处找到代码

               class Program
                  {
                      static void Main(string[] args)
                      {
                          int sum=0;
                          int max=0;
                          int size=9;
                         string input="7, 1, 3, 1, 4, 5, 1, 3, 6";
                         string[] values=input.Split(',');
                         int length=values.Length;
                         int k=size-1;
                         for(int i=0;i<=k;i++)
                         {
                           sum=sum+int.Parse(values[i]);
                           max=sum;
                         }
                         for(int j=0;k<length-1;j++)
                         {
                             ++k;
                          sum=(sum-int.Parse(values[j]))+int.Parse(values[k]);
                          if(sum>max)
                          max=sum;
                         }
                         Console.WriteLine(max);
                      }
                  }
              

              【讨论】:

                【解决方案7】:

                以下流程可能对您有所帮助

                1) 选择前 k 个元素并创建大小为 k 的自平衡二叉搜索树 (BST)。

                2) 为 i = 0 到 n – k 运行一个循环

                …..a) 从 BST 中获取最大元素,并打印出来。

                …..b) 在 BST 中搜索 arr[i] 并将其从 BST 中删除。

                …..c) 将 arr[i+k] 插入 BST。

                时间复杂度: 第 1 步的时间复杂度为 O(kLogk)。步骤 2(a)、2(b) 和 2(c) 的时间复杂度为 O(Logk)。由于步骤 2(a)、2(b) 和 2(c) 处于循环运行 n-k+1 次,因此完整算法的时间复杂度为 O(kLogk + (n-k+1)*Logk)也可以写成 O(nLogk)。

                【讨论】:

                • 为每个k=1,....,n 执行此操作时,O(n^2logn) 是什么。低于 OP 的解决方案。使用滑动窗口在 O(n) 中找到 k 个相邻元素的最大总和。不需要O(nlogk) tree 解决方案。
                • -1。我可以解决 O(N) 中固定 k 的子问题。问题的关键是对于从1到n的每个k都需要最大和的k子数组。
                猜你喜欢
                • 2021-04-11
                • 2013-03-05
                • 2013-08-06
                • 2019-06-04
                • 2020-11-11
                • 2019-12-25
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多