劍指offer-連續子數組的最大和:動態規劃及優化、貪心法、分治法(JavaScript實現)

雖然這題在 leetcode 上標註的是「簡單」難度,可是解法有 4 種,而且都很是具備表明性。比較容易想到的是基礎的動態規劃法。javascript

解法 1:動態規劃

定義狀態數組dp[i]的含義:數組中元素下標爲[0, i]的連續子數組最大和。java

狀態轉移的過程以下:git

  • 初始狀況:dp[0] = nums[0]
  • nums[i] > 0,那麼 dp[i] = nums[i] + dp[i - 1]
  • nums[i] <= 0,那麼 dp[i] = nums[i]

代碼實現以下:github

// ac地址:https://leetcode-cn.com/problems/maximum-subarray/
// 原文地址:https://xxoo521.com/2020-03-09-max-sub-sum/
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    const dp = [];

    let res = (dp[0] = nums[0]);
    for (let i = 1; i < nums.length; ++i) {
        dp[i] = nums[i];
        if (dp[i - 1] > 0) {
            dp[i] += dp[i - 1];
        }
        res = Math.max(res, dp[i]);
    }
    return res;
};

時間複雜度和空間複雜度都是\(O(N)\)數組

解法 2:原地進行動態規劃

解法 1 中開闢了 dp 數組。其實在原數組上作修改,用nums[i]來表示dp[i]。因此解法 1 的代碼能夠優化爲:優化

// ac地址:https://leetcode-cn.com/problems/maximum-subarray/
// 原文地址:https://xxoo521.com/2020-03-09-max-sub-sum/
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    let res = nums[0];
    for (let i = 1; i < nums.length; ++i) {
        if (nums[i - 1] > 0) {
            nums[i] += nums[i - 1];
        }
        res = Math.max(res, nums[i]);
    }
    return res;
};

不用開闢額外空間,因此空間複雜度降爲\(O(1)\)ui

解法 3:貪心法

貪心法的題目比較少見,並且通常都比較難證實。本題的貪心法的思路是:在循環中找到不斷找到當前最優的和 sum。spa

注意:sum 是 nums[i]sum + nums[i]中最大的值。這種作法保證了 sum 是一直是針對連續數組算和。code

代碼實現以下:blog

// ac地址:https://leetcode-cn.com/problems/maximum-subarray/
// 原文地址:https://xxoo521.com/2020-03-09-max-sub-sum/
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    let maxSum = (sum = nums[0]);
    for (let i = 1; i < nums.length; ++i) {
        sum = Math.max(nums[i], sum + nums[i]);
        maxSum = Math.max(maxSum, sum);
    }
    return maxSum;
};

空間複雜度爲\(O(1)\),時間複雜度爲\(O(N)\)

解法 4: 分治法

分治法的作題思路是:先將問題分解爲子問題;解決子問題後,再將子問題合併,解決主問題。

使用分治法解本題的思路是:

  • 將數組分爲 2 部分。例如 [1, 2, 3, 4] 被分爲 [1, 2][3, 4]
  • 經過遞歸計算,獲得左右兩部分的最大子序列和是 lsum,rsum
  • 從數組中間開始向兩邊計算最大子序列和 cross
  • 返回 max(lsum, cross, rsum)

總體過程能夠參考來自 Leetcode 官方題解的圖:

能夠看到,分治法可行的關鍵的是:最大子序列和只可能出如今左子數組、右子數組或橫跨左右子數組 這三種狀況下。

代碼實現以下:

// ac地址:https://leetcode-cn.com/problems/maximum-subarray/
// 原文地址:https://xxoo521.com/2020-03-09-max-sub-sum/

/**
 * @param {number[]} nums
 * @param {number} left
 * @param {number} right
 * @param {number} mid
 * @return {number}
 */
function crossSum(nums, left, right, mid) {
    if (left === right) {
        return nums[left];
    }

    let leftMaxSum = Number.MIN_SAFE_INTEGER;
    let leftSum = 0;
    for (let i = mid; i >= left; --i) {
        leftSum += nums[i];
        leftMaxSum = Math.max(leftMaxSum, leftSum);
    }

    let rightMaxSum = Number.MIN_SAFE_INTEGER;
    let rightSum = 0;
    for (let i = mid + 1; i <= right; ++i) {
        rightSum += nums[i];
        rightMaxSum = Math.max(rightMaxSum, rightSum);
    }

    return leftMaxSum + rightMaxSum;
}

/**
 * @param {number[]} nums
 * @param {number} left
 * @param {number} right
 * @return {number}
 */
function __maxSubArray(nums, left, right) {
    if (left === right) {
        return nums[left];
    }

    const mid = Math.floor((left + right) / 2);
    const lsum = __maxSubArray(nums, left, mid);
    const rsum = __maxSubArray(nums, mid + 1, right);
    const cross = crossSum(nums, left, right, mid);

    return Math.max(lsum, rsum, cross);
}

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    return __maxSubArray(nums, 0, nums.length - 1);
};

時間複雜度是\(O(NlogN)\)。因爲遞歸調用,因此空間複雜度是\(O(logN)\)

更多資料

整理不易,若對您有幫助,請給個「關注+點贊」,您的支持是我更新的動力 👇

相關文章
相關標籤/搜索