classSolution{ publicstaticintsplitArray(int[] nums, int m){ int n = nums.length; int[] count = newint[n + 1]; //利用前缀和数组进行优化 for(int i = 1; i <= n; i++){ count[i] = count[i - 1] + nums[i - 1]; } // num 数组最多分成 n 个子数组 return split(count, m > n ? n : m - 1, 0, n - 1); }
publicstaticintsplit(int[] count, int m, int start, int end){ //判断是否还需要切分,否则返回[start, end]范围内的元素之和 if(m == 0){ return count[end + 1] - count[start]; }
int min = Integer.MAX_VALUE; //枚举每一个切分位置 for(int i = start + 1; i <= end && m > 0; i++){ //递归地计算[i, end]分割为 m - 1个子数组的最大值的最小值 int right = split(count, m - 1, i, end); //维护最小值 min = Math.min(min, Math.max(count[i] - count[start], right)); } return min; } }
时间复杂度O(n^m),空间复杂度O(n)。爆炸!!!
方法二:动态规划
令f[i][j]表示将数组的前 i 个数分割为 j 段所能得到的最大连续子数组和的最小值。我们枚举第 j 段的具体范围,我们可以枚举 k,其中前 k 个数被分割为 j - 1 段,而第 k + 1到第 i 个数为第 j 段。此时,这 j 段子数组中和的最大值就等于 f[k][j - 1]和sub(k + 1, i)中的较大值,其中sub(i, j)表示数组 nums 中下标落在区间[i, j]内的数的和。得到状态转移方程: $$ f[i, j] = \min^{i-1}_{k=0}{max(f[k][j - 1],sub(k + 1, i))} $$ i 个数最多只能分成 i 段,因此 i >= j 是合法状态,对于不合法的状态(j > i),由于我们求的是最小值,我们将其初始化为一个很大的值,当我们尝试从不合法的状态转移,得到的结果将是一个很大的数。
classSolution{ publicintsplitArray(int[] nums, int m){ int left = 0, right = 0; // 初始化二分查找上下界 for(int i = 0; i < nums.length; i++){ right += nums[i]; if(left < nums[i]){ left = nums[i]; } }
while(left < right){ int mid = left + (right - left) / 2; if(check(nums, mid, m)){ right = mid; //存在最大分割子数组的和小于 x,将 x 缩小 }else{ left = mid + 1;//不存在最大分割子数组的和小于 x,将 x 增大 } } return left; } //检查是否存在最大分割子数组的和 小于 x publicbooleancheck(int[] nums, int x, int m){ int sum = 0; int cnt = 1; for(int i = 0; i < nums.length; i++){ // sum 值大于 x,将当前值作为新的分割子数组的开头 if(sum + nums[i] > x){ cnt++; sum = nums[i]; }else{ sum += nums[i]; } } return cnt <= m; } }