[LeetCode] 410. Split Array Largest Sum 分割數組的最大值


 

Given an array which consists of non-negative integers and an integer m, you can split the array into m non-empty continuous subarrays. Write an algorithm to minimize the largest sum among these m subarrays.

Note:
Given m satisfies the following constraint: 1 ≤ m ≤ length(nums) ≤ 14,000.

Examples:

Input:
nums = [7,2,5,10,8]
m = 2

Output:
18

Explanation:
There are four ways to split nums into two subarrays.
The best way is to split it into [7,2,5] and [10,8],
where the largest sum among the two subarrays is only 18.

 

這道題給了我們一個非負數的數組 nums 和一個整數m,讓把數組分割成m個非空的連續子數組,讓最小化m個子數組中的最大值。開始以為要用博弈論中的最小最大化算法,可是想了半天發現並不會做,於是后面決定采用無腦暴力破解,在 nums 中取出所有的m個子數組的情況都找一遍最大值,為了加快求子數組和的運算,還建立了累計和數組,可以還是 TLE 了,所以博主就沒有辦法了,只能上網參考大神們的解法,發現大家普遍使用了二分搜索法來做,感覺特別巧妙,原來二分搜索法還能這么用,厲害了我的哥。首先來分析,如果m和數組 nums 的個數相等,那么每個數組都是一個子數組,所以返回 nums 中最大的數字即可,如果m為1,那么整個 nums 數組就是一個子數組,返回 nums 所有數字之和,所以對於其他有效的m值,返回的值必定在上面兩個值之間,所以可以用二分搜索法來做。用一個例子來分析,nums = [1, 2, 3, 4, 5], m = 3,將 left 設為數組中的最大值5,right 設為數字之和 15,然后算出中間數為 10,接下來要做的是找出和最大且小於等於 10 的子數組的個數,[1, 2, 3, 4], [5],可以看到無法分為3組,說明 mid 偏大,所以讓 right=mid,然后再次進行二分查找,算出 mid=7,再次找出和最大且小於等於7的子數組的個數,[1,2,3], [4], [5],成功的找出了三組,說明 mid 還可以進一步降低,讓 right=mid,再次進行二分查找,算出 mid=6,再次找出和最大且小於等於6的子數組的個數,[1,2,3], [4], [5],成功的找出了三組,嘗試着繼續降低 mid,讓 right=mid,再次進行二分查找,算出 mid=5,再次找出和最大且小於等於5的子數組的個數,[1,2], [3], [4], [5],發現有4組,此時的 mid 太小了,應該增大 mid,讓 left=mid+1,此時 left=6,right=6,循環退出了,返回 right 即可,參見代碼如下:

 

解法一:

class Solution {
public:
    int splitArray(vector<int>& nums, int m) {
        long left = 0, right = 0;
        for (int i = 0; i < nums.size(); ++i) {
            left = max(left, (long)nums[i]);
            right += nums[i];
        }
        while (left < right) {
            long long mid = left + (right - left) / 2;
            if (can_split(nums, m, mid)) right = mid;
            else left = mid + 1;
        }
        return right;
    }
    bool can_split(vector<int>& nums, long m, long sum) {
        long cnt = 1, curSum = 0;
        for (int i = 0; i < nums.size(); ++i) {
            curSum += nums[i];
            if (curSum > sum) {
                curSum = nums[i];
                ++cnt;
                if (cnt > m) return false;
            }
        }
        return true;
    }
};

 

上面的解法相對來說比較難想,在熱心網友 perthblank 的提醒下,再來看一種 DP 的解法,相對來說,這種方法應該更容易理解一些。建立一個二維數組 dp,其中 dp[i][j] 表示將數組中前j個數字分成i組所能得到的最小的各個子數組中最大值,初始化為整型最大值,如果無法分為i組,那么還是保持為整型最大值。為了能快速的算出子數組之和,還是要建立累計和數組,難點就是在於推導狀態轉移方程了。來分析一下,如果前j個數字要分成i組,那么i的范圍是什么,由於只有j個數字,如果每個數字都是單獨的一組,那么最多有j組;如果將整個數組看為一個整體,那么最少有1組,所以i的范圍是[1, j],所以要遍歷這中間所有的情況,假如中間任意一個位置k,dp[i-1][k] 表示數組中前k個數字分成 i-1 組所能得到的最小的各個子數組中最大值,而 sums[j]-sums[k] 就是后面的數字之和,取二者之間的較大值,然后和 dp[i][j] 原有值進行對比,更新 dp[i][j] 為二者之中的較小值,這樣k在 [1, j] 的范圍內掃過一遍,dp[i][j] 就能更新到最小值,最終返回 dp[m][n] 即可,博主認為這道題所用的思想應該是之前那道題 Reverse Pairs 中解法二中總結的分割重現關系 (Partition Recurrence Relation),由此看來很多問題的本質都是一樣,但是披上華麗的外衣,難免會讓人有些眼花繚亂了,參見代碼如下:

 

解法二:

class Solution {
public:
    int splitArray(vector<int>& nums, int m) {
        int n = nums.size();
        vector<long> sums(n + 1);
        vector<vector<long>> dp(m + 1, vector<long>(n + 1, LONG_MAX));
        dp[0][0] = 0;
        for (int i = 1; i <= n; ++i) {
            sums[i] = sums[i - 1] + nums[i - 1];
        }
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                for (int k = i - 1; k < j; ++k) {
                    long val = max(dp[i - 1][k], sums[j] - sums[k]);
                    dp[i][j] = min(dp[i][j], val);
                }
            }
        }
        return dp[m][n];
    }
};

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/410

 

類似題目:

Reverse Pairs

 

參考資料:

https://leetcode.com/problems/split-array-largest-sum/

https://leetcode.com/problems/split-array-largest-sum/discuss/89816/DP-Java

https://leetcode.com/problems/split-array-largest-sum/discuss/89873/binary-search-c-solution

https://leetcode.com/problems/split-array-largest-sum/discuss/89817/Clear-Explanation%3A-8ms-Binary-Search-Java

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM