【刷題筆記】LeetCode-53 最大子陣列和

Merakii發表於2024-03-11

題目:給你一個整數陣列 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

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

暴力法

水平不夠,哐哐暴力就完事了。
程式碼:

class Solution
{
public
    int maxSubArray(int[] nums)
    {
        int result = Integer.MIN_VALUE; //初始化為int型別最小的值
        for (int i = 0; i < nums.length; i++) 
        {
            int sum = 0;
            for (int j = i; j < nums.length; j++)
            {
                sum += nums[j];
                if (sum > result)
                {
                    result = sum;
                }
            }
        }
        return result;
    }
}

思路大概是這樣的:定義兩個指標,i 指標固定指向陣列第一個元素,j 指標從i 指標的位置不斷往後遍歷,這樣對於一個陣列[-2,1,-3,4,-1,2,1,-5,4]我們就可以獲得:

-2
-2, 1
-2, 1, -3
-2, 2, -3, 4
...

再定義一個sum區域性變數,將從 i 開始每一次獲取到的元素累加起來,並判斷sum是否大於result,如果sum > result 即可賦值給result,否則result保留。透過這樣的方式最後我們的result裡面一定是最大和。

從第一個元素開始遍歷完畢後,i 指標即可指向下一個元素,sum清零,j 指標再次從 i 指標的地方向後遍歷,迴圈往復...

分治法

非常巧妙的方法。
程式碼:

class Solution {
    public int maxSubArray(int[] nums) {

        return getMax(nums, 0, nums.length - 1); //分治函式
    }
 /**
 * @param nums  輸入的陣列。
 * @param left  範圍的左邊界。
 * @param right 範圍的右邊界。
 * @return      返回三個當中最大的值。
 */
    private int getMax(int[] nums, int left, int right) {
        if (left == right) {
            return nums[left];
        }
        int mid = (left + right) / 2; //分界線mid
        int leftMax = getMax(nums, left, mid); //左
        int rightMax = getMax(nums, mid + 1, right); //右
        int crossMax = getCrossMax(nums, left, right); //中間
        return Math.max(Math.max(leftMax, rightMax), crossMax);
    }
	
    private int getCrossMax(int[] nums, int left, int right) {
        int mid = (left + right) / 2; 
        int leftSum = nums[mid]; 
        int leftMax = leftSum;
        for (int i = mid - 1; i >= left; i--) {
            leftSum += nums[i];
            if (leftSum > leftMax)
                leftMax = leftSum;
        }
        int rightSum = nums[mid + 1];
        int rightMax = rightSum;
        for (int i = mid + 2; i <= right; i++) {
            rightSum += nums[i];
            if (rightSum > rightMax)
                rightMax = rightSum;
        }
        return leftMax + rightMax; //返回中間能取到的最大值
    }
}

假設:
給定陣列 [-9,-6,-1,2,3] 一分為二,[-9,-6,-1(中間)2,3] 中間左邊的最大子序列的和是-1,右邊的最大子序列的和是 5 , 左右合併以後最大子序列和依舊是 5。
給定陣列 [-1,-9,2,3,4,-6] 將它從中間一分為二,[-1,-9,2(中間)3,4,-6] 中間左邊的最大子序列的和是2 ,右邊的最大子序列的和是 7 , 左右合併以後最大子序列和變為2、3、4組成的9。
結論:如果將一個陣列一分為二,那麼它的最大子序列將有三種情況:
一分為二
|-------在左邊
|-------在中間
|-------在右邊

分治法(Divide and Conquer)將一個難以直接解決的大問題,分解(Divide)成一些規模較小的相同問題,以便更容易地解決這些小問題,然後將這些小問題的解合併(Conquer)來解決原來的大問題。

特別要注意,中間這個子序列和是以中間線向左邊發展遞迴找到的子序列和 + 以中間線向右邊發展遞迴找到的子序列和。

動態規劃

除了暴力法第一個應該想到的方法。
程式碼:

 class Solution {
    public int maxSubArray(int[] nums) {
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        int result = nums[0];
        for(int i = 1; i < nums.length; i++){
            dp[i] = Math.max(dp[i-1] + nums[i], nums[i]);
            result = Math.max(result, dp[i]); //取最大子序列和
        }
        return result; 
    }
}

[-2,1,-3,4,-1,2] 每一步找到最大的子序列和。
1.和前面的子序列合併
2.不和前面的子序列合併,取自己的值

-2 它本身-2(取-2)
1 合併-1 不合並1(取1)
-3 合併-2 不合並-3 (取-2)
4 合併2 不合並4 (取4)
-1 合併3 不合並-1 (取3)
2 合併5 不合並2 (取5)

有了每一步的最大子序列和,我們只需要把其中的最大值取出來,就是陣列的最大子序列和。

三要素
|--------方程式:max(本身的值,前面能取到最大的序列和的值+本身的值)
|--------初始值:第一個值-2
|--------終止值:遍歷完畢為止

相關文章