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 };