【问题标题】:maximum sum of n consecutive elements of array数组的 n 个连续元素的最大和
【发布时间】:2015-10-31 16:23:40
【问题描述】:

如何求一个数组中n连续数的最大和?例如,如果我们的数组是{2,5,3,4,6}n == 2,那么输出应该是10(即6 + 4)。

我能够为数组大小的小值和n 的小值获得正确的逻辑。但是当数组大小和 n 太大(例如 105 左右时,我的代码会花费很多时间。请提出优化方法。

我的代码被剪掉了:

for(int i = 0; i <= n - h; i++) {
  int count = 0;
  for(int k = i; k < i + h; k++) {
    count = count + arr[k];
  }
  if(i == 0) {
    ans[z] = count;
  } else if(i != 0) {
    if(count < ans[z]) {
      ans[z] = count;
    }
  }
  count = 0;
}

【问题讨论】:

  • 抱歉错误。它的最大值
  • 我的第一种方法是从0到上界遍历数组一次,计算前2项的总和,并与前一个总和进行比较。你的代码怎么样?
  • for(int i=0;i&lt;=n-h;i++) { int count=0; for(int k=i;k&lt;i+h;k++) { count=count+arr[k]; } if(i==0) { ans[z]=count; } else if(i!=0) { if(count&lt;ans[z]) { ans[z]=count; } } count=0; } 这是我的代码 sn-p
  • 你的代码被什么语言截断了? (在我看来像 C 或 C++。)
  • 你可以参考我下面的示例代码,它不使用嵌套循环来计算下一个子数组的总和

标签: arrays algorithm optimization


【解决方案1】:

这是我的想法:从0遍历数组到(数组长度-N),并使用以下表达式确定下一个N-item的总和:
下一个N-item的总和=上一个sum - 前一个子数组中的第一项 + 下一个子数组中的最后一项

示例:

数组 = {2,5,3,4,6}

当 i = 0 时,总和 = (2 + 5) = 7,最大总和 = 7

当 i = 1 时,sum = 7 - 2 + 3 = 8,因为 8 > 7,所以 max sum = 8

当 i = 2 时,总和 = 8 - 5 + 4 = 7,因为 7

当 i = 3 时,sum = 7 - 3 + 6 = 10,因为 10 > 8,所以 max sum = 10

下面是c#中的示例代码

static int GetLargestSum(int[] array, int n)
{
    int largestSum = 0;
    int previousSum = 0;

    for (int i = 0; i <= array.Length - n; i++)
    {
        if (i == 0)
        {
            for (int j = 0; j < n; j++)
            {
                largestSum += array[j];
            }

            previousSum = largestSum;
        }
        else
        {
            int currentSum = previousSum - array[i - 1] + array[i + n - 1];
            if (currentSum > largestSum)
            {
                largestSum = currentSum;
            }
            previousSum = currentSum;
        }
    }

    return largestSum;
}

【讨论】:

  • 不应该是“[...] + last item in next sub-array”,因此在您的 elseint currentSum = previousSum - array[i - 1] + array[i + n - 1]; 中?
  • @terryfkjc 我需要一种更优化的方法,因为对于较大的 n 和数组大小值,这需要大量时间。
  • @swap96 我不确定您需要如何“优化”,您是否根据实际情况进行了基准测试?当n很大,并且数组没有排序时,最优化的方式应该是O(n)
  • 我的数组大小和'n'可以达到10^6。
  • 我会在滑动窗口循环(以 i = 1 开头)之外使用介绍 sum-the-first-n 循环来编写它。无论如何,您希望编译器以这种方式对其进行重组。另外,@das-g 是对的:您的滑动窗口中有一个错误。您减去的元素必须与您要添加的元素相距n+1。目前,距离始终为 2。
【解决方案2】:
function maxSubelementsSum(inputArray, count) {
  let maxSum = 0;
  for (let i = 0; i < count; i++) {
    maxSum += inputArray[i];
  }
  let tempSum = maxSum;
  for (let i = count; i < inputArray.length; i++) {
    tempSum = tempSum + inputArray[i] - inputArray[i - count];
    maxSum = Math.max(tempSum, maxSum);

  }
  return maxSum;
}

【讨论】:

    【解决方案3】:

    使用 Java 8。

    int[] numArr = {1,3,5,6,7,12,14,2,3,4}; 
    List<Integer> intList = Arrays.stream(numArr).boxed().collect(Collectors.toList());         
    List<Integer> intSumList = new ArrayList<>();
    for(int i=0; i<=numArr.length-3; i++)
    {
        int intSum = intList.stream().skip(i).limit(3).mapToInt(Integer::intValue).sum();       
        intSumList.add(Integer.valueOf(intSum));
    }       
    int maxConsecutiveSum = intSumList.stream().reduce(Integer::max).get(); 
    System.out.println("Max sum using 3 consecutive integers :"+maxConsecutiveSum);
    

    【讨论】:

      【解决方案4】:

      有很多方法可以解决这个问题。
      到目前为止,我能想到的最优化的是 O(n)。
      您可以在哪里遍历数组一次。

      我使用了滑动窗口模式来解决这个问题

      以下是解决方案背后的主要思想:

    • 获取第一个 n 的总和并作为临时最大值存储在变量中
    • 将您的 ma​​x 设置为 临时最大
    • 遍历数组
    • 在循环中的任何点,从临时最大值中减去前一个元素
    • 并将下一个n索引中的元素添加到临时最大值
    • 最后将您的主要 最大值临时最大值 进行比较
    • 如果最大值较小,则将最大值设置为临时最大值
    • function maxSubarraySum(array, num){
      
          let tempMax = 0
          
          for(let i=0; i<num; i++){
              tempMax += array[i] 
          }
          
          let max = tempMax
      
      
      
          for(i=1; i<array.length - (num-1); i++){
      
              // Substract the element you are at from the max
              // Add the element at the ith + num
              // compare with the max and reinitialize
      
              let subs = tempMax - array[i-1]
      
              tempMax = subs + array[i + num - 1]
      
              if(max < tempMax){
                 max = tempMax
              }
                
          }
      
          return(max)
          
      }
      
      

      【讨论】:

        【解决方案5】:

        您的技术可能的主要改进是滑动窗口。整数加法有一个逆减法。这使得可以从总和中删除元素并添加新元素,而不是从头开始重新计算。 (Terryfkjc's answer demonstrates this)。

        正如 Terryfkjc 对他的回答所评论的那样,存在线程级并行性。您可以让不同的线程检查数组的不同部分。如果滑动窗口的大小超过数组大小的一半,那么求和第一个n 应该是线程的。当n 大约是数组大小的一半时,在线程之间划分工作是最棘手的。大得多,它不能滑得那么远。小得多,而且大部分工作只是滑动。

        如果您的目标是带有向量指令的 CPU,我们可以使用 SIMD 轻松地向量化第一个 w(窗口大小/宽度)元素的初始总和。例如,使用 x86 SSE(C/C++ 内在函数):

        #include <immintrin.h>
        const __m128i *array_vec = (__m128i*)array;
        __m128i sums = _mm_loadu_si128(array_vec);  // or start with _mm_setzero_si128()
        
         // careful to get the loop start/end and increment correct,
         // whether you count by vectors (i++) or by elements (i+=4)
         // You may need a scalar cleanup at the end if window width isn't a multiple of the vector width
        for (int i=1; i < width/4 ; i+=1) {
            sums = _mm_add_epi32(sums, _mm_loadu_si128(array_vec+i));
            // if you know the input is aligned, and you're going to compile without AVX/AVX2, then do:
            // sums = _mm_add_epi32(sums, array_vec[i]);
        }
        __m128i hisums = _mm_bsrli_si128(sums, 8);  // get the high 2 elements into the low 2 elements of a separate vector
        sums = _mm_add_epi32(sums, hisums);  // one step of horizontal merging.    
        hisums = _mm_bsrli_si128(sums, 4);  // actually, pshufd would be faster for this, since it doesn't need a mov to preserve sums.
        sums = _mm_add_epi32(sums, hisums);
        
        int window_sum = _mm_cvtsi128_si32(sums);  // get the low 32bit element 
        

        我认为不可能矢量化滑动窗口部分。我们不能有效地滑动 4 个单独的窗口,因为每个窗口(向量元素)都需要查看每个数组元素。

        但是,如果我们对 4/8/16 种不同的窗口大小(最好是连续的)感兴趣,那么我们可以使用向量来实现。因此,在每次循环迭代中,我们都有一个带有当前滑动窗口总和的向量。使用 SSE4.1 pmaxsd(对于有符号的 32 位整数),我们可以做到

        window_max = _mm_max_epi32(window_max, window_sums);
        

        滑动窗口操作变为:添加{ a[i], a[i+1], a[i+2], a[i+3] },同时从所有4个向量元素中删除相同的数组元素。 (或者,广播要添加的元素,并减去不同元素的向量。)

        __m128i add_elements = _mm_loadu_si128((__m128i*)&array[i]);  // load i, i+1, etc.
        __m128i sub_element = _mm_cvtsi32_si128(array[i-width-1]);
          sub_element = _mm_shuffle_epi32(sub_element, 0); // broadcast the low element of the vector to the other positions.
          // AVX2: use _mm_broadcastd_epi32
          // AVX512 has a broadcast mode for memory references that can be used with most instructions.  IDK how to use it with intrinsics.
        __m128i diff = _mm_sub_epi32(add_elements, sub_element);
        window_sums = _mm_add_epi32(window_sums, diff);
        

        正确获取循环开始/结束条件,并正确考虑最后几个元素,始终是向量的挑战。

        有关如何在 x86 上使用向量的更多信息,请参阅 https://stackoverflow.com/tags/x86/info

        【讨论】:

        • 也许可以用 SSE/AVX 计算前缀和,然后我们可以很容易地计算出向量化的 max(S[i+n] - S[i])方式。
        • @stgatilov。如果您可以在写入数组时动态生成前缀总和,或者如果您想要几种不同的窗口大小,那么前缀总和将很有用。否则,我认为除非它适合 L2 (256k),否则我认为 2-pass 算法注定会因内存带宽而失败。而且,我不认为前缀和可以向量化,因为元素之间没有独立性。每个p[i]都需要看到a[i-i]a[i-2]等,所以每一步都需要横向操作。我认为这与尝试直接矢量化滑动窗口相同的问题。
        • @stgatilov:原来你可以 SIMD 一个前缀和,但它需要大量的改组。也许只值得花车,其中添加延迟更高。另见stackoverflow.com/questions/10587598/…。您还可以使用线程并行化前缀和,但必要的访问模式对于 SIMD 向量来说并不好。 http.developer.nvidia.com/GPUGems3/gpugems3_ch39.html.
        【解决方案6】:

        使用滑动窗口,其中窗口大小为2

        arr = [2,5,3,4,6];
        
        sum = 0;
        temp_sum = 0;
        
        const add = (a,b) => a+b;
        
        for(let i=0;i<arr.length-1;i++){
          console.log(i, i+2,arr.slice(i,i+2));
          temp_sum = add.apply(null, arr.slice(i,i+2));
          if(sum < temp_sum){
            sum = temp_sum;
          }
        }
        
        console.log(sum);
        

        【讨论】:

          【解决方案7】:

          这是一个简短的快速解决方案:

              s = 0
              arrayMaxConsecutiveSum = (a, k) =>
              Math.max(...
                  a.map(x => s += x - ~~a[~--k])
              )
          

          【讨论】:

            【解决方案8】:

            使用滑动窗口技术

            int curr_sum = 0, max_sum = 0;
            for(int i = 0; i < n; i++)
            {
                 curr_sum += v[i];
            }
            max_sum = curr_sum;
            for(int i = k; i < v.size(); i++)
            {
                curr_sum += v[i] - v[i-k];
                max_sum = max(max_sum, curr_sum);
            }
            
            cout<<max_sum;
            

            时间复杂度:θ(n)

            辅助空间:O(1)

            解释: [ 1 8 30 -5 20 7] 和 n = 3

            从0到n-1(0到2)curr_sum = 1+8+30 = 39

            max_sum = 39

            从r n 到 v.size()-1(3 到 5)

            curr_sum = 上一个总和 + 新元素 - 上一个窗口第一个元素

            curr_sum = 39 +(-5)-1 = 33

            max_sum = max(39,33) = 39

            curr_sum = 33 + 20 - 8 = 45

            max_sum = max(39,45) = 45

            curr_sum = 45 + 7 - 30 = 22

            max_sum = max(45,22) = 45

            【讨论】:

              【解决方案9】:

              我们用滑动窗口的方法,对大小为n的子数组求和,然后遍历数组直到len(arr) - n 并进行简单的比较,可以考虑这个方案:

              def max_sum(arr, window):
                  n = len(arr)
              
                  if (n <= window):
                      return -1
              
                  window_sum = sum([arr[i] for i in range(window)])
                  maxSum = window_sum
              
                  for i in range(n - window):
                      window_sum = window_sum - arr[i] + arr[i + window]
                      maxSum = max(window_sum, maxSum)
                  
                  return maxSum
              

              【讨论】:

                【解决方案10】:
                def maxSubarraySum(array, noOfelements):
                    start = 0
                    sum = 0
                    nextElement = 1
                    maxSum = []
                    while noOfelements < len(array)+1:
                        for i in range(start, noOfelements):
                            sum += array[i]
                        maxSum.append(sum)
                        sum = 0
                        start = nextElement
                        nextElement += 1
                        noOfelements += 1
                    return max(maxSum) if maxSum else None
                
                
                def sub(array,ele):
                    maxSum = []
                    end = ele
                    for i in range(len(array)):
                        if end <= len(array):
                            maxSum.append(sum(array[i:end]))
                            end += 1
                    return max(maxSum) if maxSum else None
                
                def sub1(array,ele):
                    tot = [sum(array[i:ele+i]) for i in range(len(array)) if ele+i <= 
                    len(array)]
                    return max(tot) if tot else None
                
                if __name__ == '__main__':
                    print(maxSubarraySum([-1, -2, -5, -3, -8, -1, -5], 4) == -11)
                    print(maxSubarraySum([1, -2, 5, 3, -8, 1, 5], 4) == 7)
                    print(maxSubarraySum([1, 2, 5, 3, 8, 1, 5], 4) == 18)
                    print(maxSubarraySum([1, 2, 5, 3, 8, 1, 5], 2) == 11)
                    print(maxSubarraySum([], 4) == None)
                

                复杂度 = O(n*n) 这适用于所有给定的数组,包括负数。

                用 O(n) 增加了一个 - 子函数

                另一个使用列表推导。 - Sub1 函数

                【讨论】:

                  【解决方案11】:

                  最简单的

                  A=list(map(int,input().split(" ")))
                  n=len(A)
                  max=0
                  for i in range(n-1):
                      if(A[i]+A[i+1]>max):
                          max=A[i]+A[i+1]
                  print(max)
                  

                  【讨论】:

                    【解决方案12】:

                    我们可以简单地迭代数组并找到两个连续数字的和

                    let array = [2,5,3,4,6];
                    let sum=0;
                    let maxNumber = 0;
                    for(let i=0; i<array.length;i++){
                        sum = array[i] + array[i+1];
                        if(sum >= maxNumber){
                            maxNumber = sum;
                        }
                        
                    }
                    console.log("max of two digit :", maxNumber);
                    

                    【讨论】:

                      【解决方案13】:

                      我建议你的第一件事是使用合并排序对数组进行排序,这具有 O(n log(n)) 的复杂时间,然后你只需要找到前 n 个连续数字,将数组从较高的数字迭代到较低的保留当前连续数字的总和,这将花费 O(n) 的复杂度时间。问候

                      【讨论】:

                      • 如果我们在计算总和之前对数组进行排序,我们将在 {2,5,3,4,6} 中得到 11 而不是 10
                      • 如果对数组进行排序,则不会保留顺序。并且选择前 n 个元素并不能保证它是给定数组的连续子数组。
                      猜你喜欢
                      • 2015-11-02
                      • 2015-11-08
                      • 1970-01-01
                      • 2011-05-28
                      • 2019-10-05
                      • 2020-08-09
                      • 1970-01-01
                      • 2015-10-31
                      • 1970-01-01
                      相关资源
                      最近更新 更多