[LeetCode] 1031: Maximum sum of two non-overlapping subarrays

原題目

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:bash

  • 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:app

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:ui

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 的,且無重複元素的子數組的元素之和的最大值。spa

也就是求 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 知足:code

  • 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
}
複製代碼

代碼解析

如何高效的求子數組的和

這道題須要找到子數組元素和的最大值,那麼確定就須要求不少不少不少不一樣子數組的元素和,而後進行比對。orm

每次都將選出來的子數組元素逐一相加確定是不合算的。ip

這裏的方法是,創建一個 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 須要調用兩次,緣由是兩個子數組長度可能不一樣,誰在線左邊,誰在線右邊,須要分狀況討論。

相關文章
相關標籤/搜索