美文网首页
LeetCode 1031: Maximum sum of tw

LeetCode 1031: Maximum sum of tw

作者: 玉儿Emily | 来源:发表于2019-06-20 18:31 被阅读0次

    原题目

    Given an array A of non-negative integers, return the maximum sum of elements in two non-overlapping (contiguous) subarrays, which have lengths L and M. (For clarification, the L-length subarray could occur before or after the M-length subarray.)

    Formally, return the largest V for which V = (A[i] + A[i+1] + ... + A[i+L-1]) + (A[j] + A[j+1] + ... + A[j+M-1]) and either:

    • 0 <= i < i + L - 1 < j < j + M - 1 < A.length, or
    • 0 <= j < j + M - 1 < i < i + L - 1 < A.length.

    Example 1:

    Input: A = [0,6,5,2,2,5,1,9,4], L = 1, M = 2
    Output: 20
    Explanation: One choice of subarrays is [9] with length 1, and [6,5] with length 2.
    

    Example 2:

    Input: A = [3,8,1,3,2,1,8,9,0], L = 3, M = 2
    Output: 29
    Explanation: One choice of subarrays is [3,8,1] with length 3, and [8,9] with length 2.
    

    Example 3:

    Input: A = [2,1,5,6,0,9,5,0,3,8], L = 4, M = 3
    Output: 31
    Explanation: One choice of subarrays is [5,6,0,9] with length 4, and [3,8] with length 3.
    

    题目翻译

    给出一个正整数数组 A,并给出两个数值 L、M;
    求这个数组中长度为 L 和 M 的,且无重复元素的子数组的元素之和的最大值。

    也就是求 V = (A[i] + A[i+1] + ... + A[i+L-1]) + (A[j] + A[j+1] + ... + A[j+M-1]) 的最大值,其中

    • (A[i] + A[i+1] + ... + A[i+L-1]) 是长度为 L 的子数组
    • (A[j] + A[j+1] + ... + A[j+M-1]) 是长度为 M 的子数组

    并且 i、j、L、M 满足:

    • 0 <= i < i + L - 1 < j < j + M - 1 < A.length, or
    • 0 <= j < j + M - 1 < i < i + L - 1 < A.length.

    答案 (JavaScript)

    /**
     * @param {number[]} A
     * @param {number} L
     * @param {number} M
     * @return {number}
     */
    var maxSumTwoNoOverlap = function(A, L, M) {
      const prefixSum = [], length = A.length
      prefixSum[0] = A[0]
      for (let i=1; i<length; i++) {
        prefixSum[i] = prefixSum[i-1] + A[i]
      }
      if (L + M === length){
        return prefixSum[length - 1];
      }
      return Math.max(split_and_find_max_sum(A, prefixSum, L, M), 
                      split_and_find_max_sum(A, prefixSum, M, L));
    };
    
    
    var split_and_find_max_sum = (A, prefixSum, L, M) => {
      const length = A.length
      const leftMax = new Array(length), rightMax = new Array(length);
      for (let i = L-1; i<length; i++) {
        const tmpSum = prefixSum[i] - prefixSum[i - L + 1] + A[i - L + 1];
        if (i === L - 1) {
          leftMax[i] = tmpSum;
        } else {
          leftMax[i] = Math.max(leftMax[i - 1], tmpSum);
        }
      }
      
      for (let i = length - M; i >= 0; i--) {
        const tmpSum = prefixSum[i + M - 1] - prefixSum[i] + A[i];
        if (i === length - M) {
          rightMax[i] = tmpSum;
        } else {
          rightMax[i] = Math.max(rightMax[i + 1], tmpSum);
        }
      }
      
      let sum = -Infinity
      for (let i = L - 1; i < length - M; i++) {
        sum = Math.max(sum, leftMax[i] + rightMax[i + 1]);
      }
      return sum
    }
    

    代码解析

    如何高效的求子数组的和

    这道题需要找到子数组元素和的最大值,那么肯定就需要求很多很多很多不同子数组的元素和,然后进行比对。

    每次都将选出来的子数组元素逐一相加肯定是不合算的。

    这里的方法是,建立一个 prefixSum 数组:

    const prefixSum = [];
    prefixSum[0] = A[0];
    for (let i=1; i<length; i++) {
      prefixSum[i] = prefixSum[i-1] + A[i]
    }
    

    这样,对于数组 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],如果我们想求 2-7 这个子数组的和,只需要用 prefixSum[6] - prefixSum[0] 就行了。

    如何避免子数组重叠

    这道题最容易想到的方法其实就是 —— 暴力搜索:

    即,将所有长度为 L 和 M 的子数组都找出来,两两配对,每一对都判断一下它们是不是重合,如果重合就舍弃;如果没有重合部分,就计算这两个数组的和,然后找出最大值即可。

    但是,效率真的低啊。。。虽然打着开心的 Accepted,但是。。。

    Runtime: 
    100 ms, faster than 16.16% of JavaScript online submissions 
    for Maximum Sum of Two Non-Overlapping Subarrays.
    

    不爽 =。=

    所以该怎么办呢?

    不用暴力搜索,那么就要聪明的搜索。
    这道题有个很关键的可以「聪明搜索」的点,就是 non-overlapping:既然必须是无重合的数组对,我们一开始只需要寻找无重合的配对来求和就可以了。

    如何寻找?

    ...怎么才能让同桌的领地和自己的领地不要重合呢?...画个三八线呗 =。=

    同理,在数组中找一条「分界线」,在这条线两边分别寻找子数组,并让每个子数组的求和尽可能大。
    最后找到所有分界情况下的子数组求和的最大值,就是答案了。

    例如:有一个数组,[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],选取数字 4,5 之间作为分界线。那么两个子数组 C、D 分别在 [1, 2, 3, 4][5, 6, 7, 8, 9, 10] 中寻找,就一定能保证数组 C、D 不重合。同理,我们还可以把分界线选在 6,7 之间...

    这样,比暴力搜索就节省了很多次无用的比较。

    函数 split_and_find_max_sum 其实就是在做画分割线 & 寻找最大可能的和这件事情。

    不过在函数中,它先分别从前往后(从后往前)计算了所有可能分界线的左边(右边)可能子元素的最大值。

    这个是从前往后找:

      for (let i = L-1; i<length; i++) {
          const tmpSum = prefixSum[i] - prefixSum[i - L + 1] + A[i - L + 1];
          if (i === L - 1) {
              leftMax[i] = tmpSum;
          } else {
              leftMax[i] = Math.max(leftMax[i - 1], tmpSum);
          }
      }
    

    这个 leftMax 就可以认为是,当分界线选了 i,i+1 之间的时候,分界线左边所有长度为 L 的子数组元素求和的最大值

    这个是从后往前找:

      for (let i = length - M; i >= 0; i--) {
          const tmpSum = prefixSum[i + M - 1] - prefixSum[i] + A[i];
          if (i === length - M) {
            rightMax[i] = tmpSum;
          } else {
            rightMax[i] = Math.max(rightMax[i + 1], tmpSum);
          }
      }
    

    rightMax 和 leftMax 基本同理。只不过 rightMax 选子数组的范围是线右边,同时数组长度是 M。

    最后一轮 for 循环才是在比对所有分界线情况,并找出最大值。

      let sum = -Infinity
      for (let i = L - 1; i < length - M; i++) {
        sum = Math.max(sum, leftMax[i] + rightMax[i + 1]);
      }
      return sum
    

    函数 split_and_find_max_sum 需要调用两次,原因是两个子数组长度可能不同,谁在线左边,谁在线右边,需要分情况讨论。

    相关文章

      网友评论

          本文标题:LeetCode 1031: Maximum sum of tw

          本文链接:https://www.haomeiwen.com/subject/ojyjqctx.html