方法一:DFS+Memoization
某种程度来说本题和 Word Break 很像。直接dfs暴力做肯定会超时,加上memoization即可。
dfs(start, m, ...) 表示从index start开始,分成m组的minmax值。
时间复杂度 比较复杂,应该和DP一样是 O(n^2*m)
class Solution { public: int splitArray(vector<int>& nums, int m) { int n=nums.size(); // sum[i] - sum of first i items vector<long> sum(n+1,0); for (int i=1;i<=n;++i){ sum[i] = sum[i-1]+nums[i-1]; } vector<vector<long>> memo(n,vector<long>(m+1,-1)); return dfs(0,m,nums,sum,memo); } // start - start index long dfs(int start, int m, vector<int> &nums, vector<long> &sum, vector<vector<long>> &memo){ if (m==1) return sum[nums.size()]-sum[start]; if (memo[start][m]!=-1) return memo[start][m]; long minmax=LONG_MAX; for (int i=start+1;i<nums.size();++i){ minmax = min(minmax, max(sum[i]-sum[start], dfs(i,m-1,nums,sum,memo))); } return memo[start][m]=minmax; } };
方法二:DP
dp[i][j] 表示前i个分成j组的minmax的值。
dp[i][j] = min_k{ max(dp[k][j-1], a[k]+...+a[i-1]) } (0<=k<i)
corner case: dp[i][j] = INT_MAX (j>i), dp[i][1] = sum[i]
前i个其实最多只能分为i份,所以如果 j>i,不可能。设为INT_MAX就不会影响到最小值。
因此可以将dp全部初始化为INT_MAX,而且 只要将 dp[0][0] 设为0,dp[i][1] = sum[i]也可以被整合进for循环里。
当然,按照corner case初始化也是可以的。而且循环k的时候也可以从后向前,如果presum太大可以提前结束循环。
https://leetcode.com/problems/split-array-largest-sum/discuss/89838/C++-DP-SOLUTION
时间复杂度 O(n^2*m)
class Solution { public: int splitArray(vector<int>& nums, int m) { int n=nums.size(); // dp[i][j] - the minmax value of dividing first i items into j parts // dp[0][j] = INT_MAX, dp[i][1] = sum[i]; // dp[i][j] = min_k max(dp[k][j-1], a[k]+...+a[i-1]) (0<=k<i) vector<vector<long>> dp(n+1,vector<long>(m+1,INT_MAX)); // sum[i] - sum of first i items vector<long> sum(n+1,0); for (int i=1;i<=n;++i){ sum[i] = sum[i-1]+nums[i-1]; } dp[0][0] = 0; for (int i=1;i<=n;++i){ for (int j=1;j<=m;++j){ for (int k=0;k<i;++k){ dp[i][j] = min(dp[i][j], max(dp[k][j-1], sum[i]-sum[k])); } } } return dp[n][m]; } };
方法三:Binary Search+Greedy
原题是Optimization问题,要找到最大subarray的最小值。我们可以先解决对应的Search Problem,然后用二分解决优化问题。即,给定一个上限cap,判断能否在满足要求的情况下(最大subarray<=cap),将数组分为m段。
这个Search Problem可以用Greedy来做,如果subarray sum>cap,说明需要新开一段。
二分部分标准写法,搜索区间是 [max{nums[i]}, sum_{nums[i]}],如果canSplit(mid),我们需要找更小的值,high=mid,如果不行,那就放松cap,low=mid+1。同时也保证了解一直在搜索区间里。
当然另一种写法 while (low<=high); low=mid+1; high=low-1; 当然也是可以的,搜索区间是 [low,high],解区间是[low,high+1]。可以复习一下 Binary Search。
时间复杂度 O(n*log(sum(nums) - max(nums)))
class Solution { public: int splitArray(vector<int>& nums, int m) { long low=0, high=0; for (long num:nums){ low = max(low,num); high += num; } while (low<high){ long mid=(low+high)/2; if (canSplit(mid,nums,m)) high = mid; else low = mid+1; } return low; } bool canSplit(long cap, vector<int> &nums, int m){ int count=1; long sum=0; for (int num:nums){ sum += num; if (sum>cap){ ++count; if (count>m) return false; sum = num; } } return true; } };