Leetcode 410. Split Array Largest Sum
Problem:
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:
If n is the length of array, assume the following constraints are satisfied:
- 1 ≤ n ≤ 1000
- 1 ≤ m ≤ min(50, n)
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.
Solution:
看到極小化極大值的問題,我的第一個想法就是動態規劃,一般來說,這類極小化極大值或極大化極小值問題大概率都是用動態規劃來做,而且還有一個特性,就是動態規劃一般會把一維問題上升到三維的時間複雜度,比如Leetcode 375。照這個思路往下走,如何進行規劃呢。首先是維護一個二維陣列,dp[i][j]表示在第j個數之前把子陣列分為i份的最小的最大值(有點繞,意思是第j個數之前可能有多種分法,每種分法都有一個和最大的子陣列,dp[i][j]即所有分法中最小的最大值),因此,如果將第j個數之前把它分為i+1份,則從i到j進行迴圈(因為第i個數之前不可能分為i份),計算dp[i][t]和sum[j]-sum[t]中的較大值,在這i到j之間尋找這個較大值的最小值賦給dp[i+1][j],程式碼如下
1 class Solution { 2 public: 3 int splitArray(vector<int>& nums, int m) { 4 int value = INT_MAX; 5 int summary = 0; 6 vector<int> sum(nums.size(),0); 7 vector<int> dp(nums.size(),INT_MAX); 8 for(int i = 0;i != nums.size();++i){ 9 summary += nums[i]; 10 sum[i] = summary; 11 } 12 for(int i = 0;i != nums.size();++i) 13 dp[i] = sum[i]; 14 for(int i = 1;i != m;++i){ 15 for(int j = nums.size()-1;j >= i;--j){ 16 value = INT_MAX; 17 for(int t = i-1;t != j;++t){ 18 value = min(value,max(dp[t],sum[j]-sum[t])); 19 } 20 dp[j] = value; 21 } 22 } 23 return dp.back(); 24 } 25 };
然而這道題還沒有結束,通過提交效率的直方圖我發現有兩個距離差的較遠的高峰,而且標籤還有一個詭異的Binary Search,這說明這道題還有一個二分搜尋的解法。首先我們可以斷定答案落在區間[max(nums),sum(nums)]之間,所以我們在這個區間內做二分搜尋,並判斷當結果為pivot時至少可以分為多少部分,找到最小可以滿足分為m份的值。
Code:
1 class Solution { 2 public: 3 int splitArray(vector<int>& nums, int m) { 4 int left = 0; 5 int right = 0; 6 for(int i = 0;i != nums.size();++i){ 7 left = max(left,nums[i]); 8 right += nums[i]; 9 } 10 while(left < right){ 11 int pivot = left+(right-left)/2; 12 int count = 1; 13 int sum = 0; 14 for(int i = 0;i != nums.size();++i){ 15 sum += nums[i]; 16 if(sum > pivot){ 17 sum = nums[i]; 18 count++; 19 } 20 } 21 if(count <= m) 22 right = pivot; 23 else 24 left = pivot+1; 25 } 26 return left; 27 } 28 };