您的位置:首页 > 其它

LeetCode 410 - Split Array Largest Sum

2016-10-20 22:29 337 查看

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,要求把序列nums分成m份,并且要让这m个子序列各自的和的最大值最小(minimize the largest sum among these m subarrays)。

一种动态规划的思路

这道题我是在动态规划的分类里面找的,所以一开始也是想用动态规划。

大致思路是考虑一个序列最右边切割的位置。在这里arr
[m]的意思是对序列arr[1…n]切割m-1次(即划分成m块)的最好结果。

这里有,arr[n][m]=minkmax(arr[k][m−1],arr[n][1]−arr[k][1]),后面这个arr[n][1]−arr[k][1]就是第k+1个数到第n个数的累加,其实就是第m块的和了。

class Solution {
public:
int splitArray(vector<int> &nums, int m) {

//corner cases...

vector<vector<int>> arr(nums.size(), vector<int>(m + 1, INT_MAX));
//m == 1
arr[0][1] = nums[0];
for (int i = 1; i < nums.size(); i++) {
arr[i][1] = arr[i - 1][1] + nums[i];
}

//m > 1
for (int m_i = 2; m_i <= m; m_i++) {
for (int n_j = m_i - 1; n_j < nums.size(); n_j++) {
arr[n_j][m_i] = INT_MAX;
for (int k = 0; k < n_j; k++) {
arr[n_j][m_i] = min(arr[n_j][m_i], max(arr[k][m_i - 1], arr[n_j][1] - arr[k][1]));
}
}
}
return arr[nums.size() - 1][m];
}
};


这个结果应该是对的,我验算过一些测试案例。但很不幸啊,TLE了。我想要不就是我这个DP太挫,还有更好的想法,要不就是这题目分类就是误导人的。

二分法查找

后来我参考了Discuss上的一些帖子,其中这篇我觉得是最浅显易懂的,写得很清楚,目测也是国人。

[C++ / Fast / Very clear explanation / Clean Code] Solution with Greedy Algorithm and Binary Search)

核心思路就是,我们观察到对于一个序列nums分割成m块,无论怎么分割,这个答案(最小值)一定在区间[max(nums), sum(nums)]之中。

比如[1,2,3,4],最好的情况是[[1],[2],[3],[4]] (你可以随意切),最坏的情况是[[1,2,3,4]] (一刀也不能切)。考虑m块,也就是m-1个切割,切割的结果无论好坏总是在上述这个区间里面。

但我们要的是最好的结果。假如可以分成m>=size(nums)块,显而易见,我们就能够取到区间最小值max(nums),但m要是小一些的话就不一定了。比如[1,2,3,4],m=2,我们不能切成两块并使得结果为4,显然这里结果是6。

那我们就只能从区间最小值开始搜索了。比如上面这个例子,我们尝试4,看看能不能找到一种切割,使得每个块的和都不大于4,这做不到吧;于是尝试5,不行;尝试6,可以了,[1,2,3],[4],那么6一定就是最后的结果。

但这样做太慢了,我们可以用二分搜索。假设我们已经构造了一个检测是否存在一个切割使得最大值为k的划分的函数(即下面代码中的
doable()
),我们可以在区间[max(nums),sum(nums)]中二分查找。因为区间符合的数肯定符合[false, false, …, false, true, … true]这种结构的,我们通过二分查找来找最小的这个true就好了。

因为作者已经提供了一份C++的代码,这里我就改用Python吧。

import math

class Solution(object):
def doable(self, nums, m, k):
acc = 0
for n in nums:
if acc + n > k:
if m == 1:
return False
acc = 0
m -= 1
acc = acc + n
return True

def splitArray(self, nums, m):
lo = max(nums)
hi = sum(nums)
while lo < hi:
mid = math.floor((lo + hi) / 2);
if self.doable(nums, m, mid):
hi = mid - 1
else:
lo = mid + 1

if self.doable(nums, m, lo):
return int(lo)
else:
return int(lo + 1)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: