有n本书和k个抄写员。要求n本书必须连续地分配给这k个抄写员抄写。也就是说前a1本书分给第一个抄写员,接下来a2本书分给第二个抄写员,如此类推(a1,a2需要你的算法来决定)。给定n,k和每本书的页数p1,p2..pn,假定每个抄写员速度一样(每分钟1页),k个抄写员同时开始抄写,问最少需要多少时间能够将所有书全部抄写完工?
样例
Given array A = [3,2,4]
, k = 2
.
Return 5
( First person spends 5 minutes to copy book 1 and book 2 and second person spends 4 minutes to copy book 3. )
解答
解法1:动态规划
设f[i][j]代表前i本书分给j个抄写员抄完的最少耗时。答案就是f[n][k]。状态转移方程f[i][j] = min{max(f[x][j-1], sum(x+1, i)), j<x<i}。其中x是在枚举第j个抄写员是从哪本书开始抄写。
时间复杂度O(n^2*k)
解法2;动态规划+决策单调。
同上一解法,但在x的枚举上进行优化,设s[i][j]为使得f[i][j]获得最优值的x是多少。有s[i][j+1]>=s[i][j]>=s[i-1][j]。因此x这一层的枚举不再是每次都是n而是总共加起来n。
时间复杂度O(n*k)
解法3:二分答案
二分答案,然后尝试一本本的加进来,加满了就给一个抄写员。看最后需要的抄写员数目是多余k个还是少于k个,然后来决定是将答案往上调整还是往下调整。其实可以这样想:如果一个人抄书那么耗时 maxTime , which is the sum of all pages (上限).
如果有足够多人,则最小耗时为所有书中最大值 (下限)
答案必然在minTime and maxTime 之间, 用 二分法找出满足条件的答案
时间复杂度O( n log Sum(pi) )
// http://www.jiuzhang.com/problem/2/
class Solution {
public:
/**
* @param pages: a vector of integers
* @param k: an integer
* @return: an integer
*/
// 解法1: 动态规划
// 设f[i][j]代表前i本书分给j个抄写员抄完的最少耗时。答案就是f[n][k]。
// 思考最后一个人需要抄几本书
// 状态转移方程f[i][j] = min{max(f[x][j-1], sum(x+1, i)), j<x<i}。其中x是在枚举第j个抄写员是从哪本书开始抄写。
// 时间复杂度O(n^2*k)
int copyBooks1(vector<int> &pages, int k) {
// write your code here
int n = pages.size(); // book number
if(n == 0){
return 0;
}
int ans = 0;
//预处理边界条件
if(k > n){
for(int i = 0; i < n; i++){
ans = max(ans, pages[i]);
}
return ans;
}
//f[i][j] 表示前i本书分给j给人抄的最少花费时间
vector<vector<int> > f(n+1, vector<int>(k+1, 0));
int maxPage = 0;
for(int i = 0; i < n; i++){
maxPage = max(maxPage, pages[i]);
}
for(int i = 1; i <= n; i++){
f[i][0] = numeric_limits<int>::max();
}
// prepare sum start
vector<int> sum(n, 0);
// sum[i] 表示从pages[0]到pages[i]的前缀和
sum[0] = pages[0];
for(int i = 1; i < n; i++){
sum[i] = pages[i] + sum[i-1];
}
// prepare sum end
for(int i = 1; i <= n; i++){
for(int j = 1; j <= k; j++){
int minTime = numeric_limits<int>::max();
for(int x = j-1; x < i; x++) { // 枚举最后一个人从哪本书开始抄
// x表示前面j-1 个人抄x本书(至少j-1本,否则不够抄),
// 最后一个人抄第x+1本(下标x)到最后第i本(下标i-1)
minTime = min(minTime,
max(f[x][j-1], sum[i-1] - sum[x-1]));
}
f[i][j] = minTime;
}
}
return f[n][k];
}
// 解法2: 二分法
// 二分答案,然后尝试一本本的加进来,加满了就给一个抄写员。
// 看最后需要的抄写员数目是多余k个还是少于k个,然后来决定是将答案往上调整还是往下调整。
// 时间复杂度O( n log Sum(pi) )
int copyBooks(vector<int> &pages, int k) {
int n = pages.size(); // book number
if(n == 0){
return 0;
}
int ans = 0;
//预处理边界条件
if(k > n){
for(int i = 0; i < n; i++){
ans = max(ans, pages[i]);
}
return ans;
}
int minTime = numeric_limits<int>::min();
int maxTime = 0;
for(int i = 0; i < n; i++){
minTime = max(minTime, pages[i]); // min of books
maxTime += pages[i];// sum of all
}
//可以这样想:如果一个人抄书那么耗时 maxTime , which is the sum of all pages (上限).
// 如果有足够多人,则最小耗时为所有书中最大值 (下限)
//答案必然在minTime and maxTime 之间
// 二分法找出满足条件的答案
int start = minTime, end = maxTime;
while(start < end){
int mid = start + (end - start) / 2;
if(search(mid, pages, k)){
// 此时已经满足条件n本书由k个人抄完,但是我们要找最小费时,所以继续往左边区间找
// 由于mid 是可能的答案之一,所以不能mid-1.
end = mid;
}
else {
// 在mid时间内无法抄完
start = mid + 1;
}
}
return start;
}
// search 函数返回值表示k个人在target 时间能否抄完所有书
bool search(int target, vector<int> &pages, int k){
int count = 1; // how many people needed
int sum = 0;
int i = 0;
while(i < pages.size()){
if(sum + pages[i] <= target){ // 每个人在target时间内尽量多抄
sum += pages[i++];
}
else if(pages[i] <= target){
count++;//上一个人抄不完,由另外一个人抄
sum = pages[i++];
}
else {
// 单本书就已经超时了,直接return
return false;
}
}
return count <= k;
}
};