一道題把我氣笑了:) 力扣.53 最大子陣列和 leetcode maximum-subarray

老马啸西风發表於2024-11-10

陣列系列

力扣資料結構之陣列-00-概覽

力扣.53 最大子陣列和 maximum-subarray

力扣.128 最長連續系列 longest-consecutive-sequence

力扣.1 兩數之和 N 種解法 two-sum

力扣.167 兩數之和 II two-sum-ii

力扣.170 兩數之和 III two-sum-iii

力扣.653 兩數之和 IV two-sum-IV

力扣.015 三數之和 IV three-sum

題目

給你一個整數陣列 nums ,請你找出一個具有最大和的連續子陣列(子陣列最少包含一個元素),返回其最大和。

子陣列是陣列中的一個連續部分。

示例 1:

輸入:nums = [-2,1,-3,4,-1,2,1,-5,4]
輸出:6
解釋:連續子陣列 [4,-1,2,1] 的和最大,為 6 。
示例 2:

輸入:nums = [1]
輸出:1
示例 3:

輸入:nums = [5,4,-1,7,8]
輸出:23

提示:

1 <= nums.length <= 10^5
-104 <= nums[i] <= 10^4

進階:如果你已經實現複雜度為 O(n) 的解法,嘗試使用更為精妙的 分治法 求解。

v1-字首和 BF

思路

看到連續子陣列和,比較自然的是想到用字首和來加速子陣列和的計算。

1)構建好字首和

2)窮舉所有可能的子陣列和,找出最大值。

實現

class Solution {
    public int maxSubArray(int[] nums) {
        final int n = nums.length;

        int[] prefixSum = new int[n];
        prefixSum[0] = nums[0];
        for(int i = 1; i < n; i++) {
            prefixSum[i] = prefixSum[i-1] + nums[i];
        }

        // BF 匹配
        int maxSum = nums[0];
        for(int i = 0; i < n; i++) {
            // 後面的陣列 》 前一個標識
            for(int j = i; j < n; j++) {
                int sum = prefixSum[j] - prefixSum[i] + nums[i];

                // 更新最大值
                 maxSum = Math.max(maxSum, sum);
            }
        }
        return maxSum;
    }
}

效果

超出時間限制

204 / 210 個透過的測試用例

v2-如何改進? 雙指標?

思路

我們之所以很慢,是因為在計算連續子陣列和的時候,計算了各種場景。但是這裡要如何最佳化呢?

但是不對比所有的,如何找到最大的呢?

最氣人的是題目中的那一句:如果你已經實現複雜度為 O(n) 的解法,嘗試使用更為精妙的 分治法 求解。

左右兩邊的雙指標可行嗎?

感覺雙指標不可行 雙指標適合計算最大的長度,但是不太適合這種最大的和。

v3-貪心

思路1

看了一眼相似題目,其中有一個是 【買賣股票的最佳時機】{簡單}

於是貪心的話,思路可以簡化為:

public int maxSubArray(int[] nums) {
    final int n = nums.length;
    // BF 匹配
    int maxSum = nums[0];
    for(int i = 1; i < n; i++) {
        // 加上當前值變大?不加當前值?
        // 變大
        int num = nums[i];
        // 無腦直接加
        if(num >= 0) {
            maxSum += num;
        } else {
            // 如果不是呢?
            // 也不能貿然丟棄 因為連續起來,後來可能又大於0的?
            // 那麼 怎麼簡單的判斷這個事情呢?
            
        }
    }
    return maxSum;
}

思路-開啟評論區

首先看到一首打油詩 被逗笑了

開啟我的題庫,調為簡單難度。

計算最大子數,直接給我難住。

報錯鋪滿螢幕,凝望沒有思路。

縫縫補補做出,擊敗零個使用者。

翻閱評論找補,令我勃然大怒。

不禁心有一問,都是人,憑什麼我——這麼廢物。

55555555

被開啟的不單單是評論區的,當然還有自己的思路。

我們整體的方向沒錯,但是這裡需要一個技巧。

如下:

/**解題思路

用 temp 記錄區域性最優值,用 result 記錄全域性最優值。
每遍歷一個新元素時,判斷(已遍歷的連續子陣列的和)加上(當前元素值),與(當前元素值)對比誰更大。
(1)如果已遍歷的連續子陣列的和 + 當前元素值 >= 當前元素值
說明(已遍歷的連續子陣列的和)是大於等於0的,令 temp = 已遍歷的連續子陣列的和 + 當前元素值。

(2)如果已遍歷的連續子陣列的和 + 當前元素值 < 當前元素值
說明(已遍歷的連續子陣列的和)是小於0的,加上這部分只會拖累當前元素,故應該直接拋棄掉這部分,令 * temp = 當前元素值。

(3)對比 temp 和 result,如果 temp 更大,則更新到 result 中。 
*/

程式碼

class Solution {
    public int maxSubArray(int[] nums) {
        final int n = nums.length;

        // BF 匹配
        int maxSum = nums[0];
        int tempSum = nums[0];
        for(int i = 1; i < n; i++) {
            int num = nums[i];

            // 歷史資料大於等於0,則保留繼續累加
            if(tempSum >= 0) {
                tempSum += num;
            } else {
                // 歷史和小於 0,直接捨棄。只保留今天
                tempSum = num;
            }

            maxSum = Math.max(maxSum, tempSum);
        }

        return maxSum;
    }
}

簡單的最佳化,我們直接判斷是否大於等於0即可,減少一次累加計算。聊勝於無。

效果

1ms 100%

效果拔群

小結

那麼這一題和股票有啥關係呢?

股票的買賣貪心其實要簡單一些,就是明天比今天高,直接無腦買賣。而且不要求連續。

這裡要求連續,就需要一個巧妙的構思,有時候不一定能很快想到。

比如我們可買賣股票無限次數上點難度,增加一個限制,買賣的天數必須是連續的天數,怎麼解?

其實就是 {買+賣} 的和當做一個數,然後就變成這一題了

v3-DP

思路

一個問題能不能被 DP 解決呢?

就看能不能拆分為遞推的子問題。

那麼,這個問題可以嗎?

遞推公式是什麼?

也就是我們還是需要想到上面那個思路。

dp[i] = Math.max(0, dp[i-1]) + nums[i];

實現

class Solution {
    public int maxSubArray(int[] nums) {
        final int n = nums.length;

        int[] dp = new int[n];
        dp[0] = nums[0];
        int maxResult = nums[0];
        for(int i = 1; i < n; i++) {
            int num = nums[i];
            dp[i] = Math.max(0, dp[i-1]) + nums[i];
            maxResult = Math.max(dp[i], maxResult);
        }

        return maxResult;
    }
}

效果

2ms 36.91%

小結

DP 的優點是使用範圍更加廣泛,這如果是一個系列的題目,不斷上難度,DP 也許可以成為一個模板。

但是如果是效能,比不上上面的 greedy。

或者說上面的貪心,是對下面遞推陣列的儲存空間最佳化。

差點掛在了第一個選擇的陣列題目上....ORZ

相關文章