描述

这是一篇面对初级coder的题解。

知识点: 堆 数学 模拟

难度: 三星


题解

题目:

有n棵苹果树,第i棵苹果树上有aia_{i}ai个果子。 计划mmm天去采摘果子。对于第iii天,它会去所有果树上轮流采摘bib_{i}bi个果子。如果对于第i天,某棵果树上没有bib_{i}bi个果子,那么它只会把当前果树上的果子采摘完。 返回一个数组 (代表每一天的真实苹果采摘量)

分析:

模拟的逻辑如下图 图片说明

需要先对果树进行排序,从而优化查找速度

方法一:暴力求解

双重遍历:

第一轮:遍历每一天,摘对应的苹果

第二轮:遍历每一棵树,通过预先按果子数进行排序,可以每次从不为空的树开始维护,从而提高搜索效率。

class Solution {
public:
    vector<long> solve(vector<int>& a, vector<int>& b) {
        vector<long> ans(b.size(),0);//返回答案数组
        sort(a.begin(),a.end());//按苹果由少到多排序
        int begin=0;//记录摘到苹果
        for(int i=0;i<b.size();i++){//遍历每一天
            for(int j=begin;j<a.size();j++){//遍历每棵树
                if(a[j]<=b[i]){//不够摘
                    ans[i]+=a[j];//只摘当前值
                    a[j]=0;
                    begin=j+1;//下次遍历从不为空的树开始
                }
                else{
                    ans[i]+=b[i];//摘预定值
                    a[j]-=b[i];//树上果子减摘取值
                }
            }
        }
        return ans;
    }
};

运行测试:

2/8 组用例通过 运行时间1001ms 占用内存308KB

复杂度分析:

时间复杂度:最坏情况下O(nlongn+mn)O(nlongn+mn)O(nlongn+mn),sort排序O(nlongn)O(nlongn)O(nlongn),加上主程序两层循环相互嵌套

空间复杂度:O(1)O(1)O(1),不占用额外存储空间,其中ans是返回函数必要空间,不属于额外空间

方法一改:排序 记录

优化上述方法,上述方法在每次采摘中需要实时维护每棵树上的果子数,而事实上,这个值无需输出,可以采用懒标记的形式维护,而当树上果子够摘时,可以直接用乘法计算结果,提高效率。

主要优化点:统一采用一个已摘取值来记录,无需维护每棵树上的果子数,排序后后面的树不再遍历

class Solution {
public:
    vector<long> solve(vector<int>& a, vector<int>& b) {
        vector<long> ans(b.size(),0);//返回答案数组
        sort(a.begin(),a.end());//按苹果由少到多排序
        int begin=0;//标记开始位
        long done=0; //已采摘
        for(int i=0;i<b.size();i++){//遍历每一天
            done+=b[i];
            for(int j=begin;j<a.size();j++){//遍历每棵树
                if(a[j]<=done){//不够摘
                    ans[i]+=a[j]-(done-b[i]);//只摘当前值
                    begin=j+1;//下次遍历从不为空的树开始
                }
                else{
                    ans[i]+=b[i]*(a.size()-j);//后面的树全部摘预定值
                    break;
                }
            }
        }
        return ans;
    }
};

运行测试:

运行时间 51ms 占用内存 6720KB

复杂度分析:

时间复杂度:最坏情况下O(nlongn+m+n)O(nlongn+m+n)O(nlongn+m+n),sort排序O(nlongn)O(nlongn)O(nlongn),主程序两层循环虽然相互嵌套,但是每次只找到不够摘的树就跳出,姑为O(m+n)O(m+n)O(m+n)

空间复杂度:O(1)O(1)O(1),不占用额外存储空间,其中ans是返回函数必要空间,不属于额外空间

方法二:堆

采用一个堆来维护苹果树,每次将不够摘的树移出堆,从而保证每个树只进出堆一次。

同时需要记录已摘的果子数,当堆顶果树值小于此值,则令其出堆

每次返回值=(出堆果子数-之前已摘总数)——可能有多个+ 堆大小*本日应采摘值

alt

class Solution {
public:
    vector<long> solve(vector<int>& a, vector<int>& b) {
        vector<long> ans(b.size(),0);
        sort(a.begin(),a.end());
        priority_queue<int, vector<int>, greater<int>> apple; //小顶堆苹果园
        long done=0; //已采摘
        for(int i=0;i<a.size();i++)//所有果树入堆
            apple.push(a[i]);
        for(int i=0;i<b.size();i++){//开始摘果子
            done+=b[i];//每棵树总计果量
            ans[i]=apple.size()*b[i];//假设全部够摘
            while(apple.size()!=0&&apple.top()<done){
                ans[i]-=done-apple.top();//减去不够摘的部分
                apple.pop();//出堆
            }
        }
        return ans;
    }
};

复杂度分析:

时间复杂度:最坏情况下O(nlongn+m+n)O(nlongn+m+n)O(nlongn+m+n),堆排序O(nlongn)O(nlongn)O(nlongn),主程序两层循环虽然相互嵌套,但是每次只找到不够摘的树就跳出,故为O(m+n)O(m+n)O(m+n)

空间复杂度:O(n)O(n)O(n),占用额外存储空间(堆的长度)

运行测试:

运行时间 60ms 占用内存 6980KB

总结

可以采用堆的结构来维护有序数组 ——如c++中提供的priority_queue

堆排序

  堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。

  堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

图片说明 alt