抄书问题

有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

时间复杂度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 &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 > f(n+1, vector(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::max();
        }
        
        // prepare sum start
        vector 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::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 &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::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 &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;
    }
};




你可能感兴趣的:(算法)