《LeetCode之每日一题》:143.IPO

有关题目

假设 力扣(LeetCode)即将开始 IPO(首次公开募股) 。
为了以更高的价格将股票卖给风险投资公司,
力扣 希望在 IPO 之前开展一些项目以增加其资本。 
由于资源有限,它只能在 IPO 之前完成最多 k 个不同的项目。
帮助 力扣 设计完成最多 k 个不同项目后得到最大总资本的方式。

给你 n 个项目。
对于每个项目 i ,它都有一个纯利润 profits[i] ,
和启动该项目需要的最小资本 capital[i] 。

最初,你的资本为 w 。
当你完成一个项目时,你将获得纯利润,且利润将被添加到你的总资本中。

总而言之,从给定项目中选择 最多 k 个不同项目的列表,
以 最大化最终资本 ,并输出最终可获得的最多资本。

答案保证在 32 位有符号整数范围内。
示例 1:

输入:k = 2, w = 0, profits = [1,2,3], capital = [0,1,1]
输出:4
解释:
由于你的初始资本为 0,你仅可以从 0 号项目开始。
在完成后,你将获得 1 的利润,你的总资本将变为 1。
此时你可以选择开始 1 号或 2 号项目。
由于你最多可以选择两个项目,所以你需要完成 2 号项目以获得最大的资本。
因此,输出最后最大化的资本,为 0 + 1 + 3 = 4
示例 2:

输入:k = 3, w = 0, profits = [1,2,3], capital = [0,1,2]
输出:6
提示:

1 <= k <= 10^5
0 <= w <= 10^9
n == profits.length
n == capital.length
1 <= n <= 10^5
0 <= profits[i] <= 10^4
0 <= capital[i] <= 10^9

题解

法一:贪心 + 优先队列 (+ 快速选择)
参考官方题解评论区下梦璃夜·天星
Tips

*max_element(capital.begin(), capital.end())
找出capital中最大元素

②nth_element(profits.begin(), profits.begin() + k, profits.end(), greater());
函数:
nth_element(a, a+i, b) 是 对[a,b)范围内进行快速选择,使得下标为i的元素排序完毕
即对数组按照cmp规则排序后,下标为i的数,放在对应位置上。

不加上第四个变量,默认求第 k 小元素,第k 元素左边可无序 且元素大小皆比第 k 小元素小,
右边元素大小皆比第 k 小元素大
注意k 从零开始

类比延伸:找出第 k 大元素,则只需找出第n - k + 1 小元素(注:这里类比链表(长 n 链表)倒数第k个,即为正数第 n - k + 1nth_element(profits.begin(), profits.begin() + n - k, profits.end());//n为数组profits大小
或者我们可以将所有元素变为负数,则第 k 大则为第 k 小

加上第四个变量:若为greater()则找出第 k 大的元素,



③iota(iCapital.begin(), iCapital.end(), 0);
iota函数对一个范围数据进行赋值,从第三个参数的初始值开始
每次加 1sort(iCapital.begin(), iCapital.end(), [&capital](int a, int b){return capital[a] < capital[b];});
对iCapital数组起始位置,之末尾位置进行排序
不填sort会默认按数组升序排序

与C语言中的qsort不同的是,对于C语言中的qsort中的第四个参数cmp来说,若 返回一个大于零的数则升序排序,
对C++中的sort函数来说则相反,返回一个大于零的数字则降序排序

本题中sort则是第三个参数自定义一个函数,按照对capital中元素升序排序来改变iCapital中元素的顺序
即下标映射排序,就可以不需要建立一份数对来排序了

class Solution {
public:
    int findMaximizedCapital(int k, int w, vector<int>& profits, vector<int>& capital) {
        int n = profits.size();
        if (w >= *max_element(capital.begin(), capital.end())){
            nth_element(profits.begin(), profits.begin() + k, profits.end(), greater());
            return w + accumulate(profits.begin(), profits.begin() + k, 0);
        }

        vector<int> iCapital(n);
        iota(iCapital.begin(), iCapital.end(), 0);
        sort(iCapital.begin(), iCapital.end(), [&capital](int a, int b){return capital[a] < capital[b];});
        priority_queue<int> pq;
        auto cur = iCapital.begin();
		
		//注意*cur为iCapital排序的从左往右的元素,capital[*cur]则未排序
        for (int i = 0; i < k; ++i){
            while(cur < iCapital.end() && capital[*cur] <= w){
                pq.push(profits[*cur++]);
            }
            if (pq.empty()){
                return w;
            }
            
                w += pq.top();
                pq.pop();
            
        }
        return w;
    }
};

时间复杂度:O[(n + k) log n],对下标数组的映射排序,所需复杂度O(n logn),堆中添加元素的 n log n
所需次数与拿出最大值的复杂度O(k log n)
若考虑快速选择,同时用BFPRT优化应该是严格O(n),不然是平均O(n) 最坏无穷大
故中的时间复杂度仍为O[(n + k)log n]
空间复杂度:O(n),所需映射下标数组大小与大根堆大小
法二:利用堆的贪心算法
Tips

priority<int>
默认的大根堆实现,内部从大到小排序,top()是当前优先队列的最大值

priority_queue<int,vector<int>,greater<int>>
最小值的优先队列,内部从小到大排序,top() 是当前优先队列的最小值。

priority_queue<int, vecotr<int>, less<int>>
最大值的优先队列,内部从大到小排序,top()为当前队列的最大值

参考官方题解

思路:
①假设不限制次数,我们启动所有满足 w >= capital[i]的项目,
来获取对应的profits[i]

②在 k 次条件下, 为了获取最大profits我们会 贪心 的 
选择在所有满足 w >= capital[i] 中 前 k 项 最大值,
而数据结构大根堆pq可以做到这点

算法细节:
具体地,我们对capital与其对应的 profits 进行排序,使用capital为第一成员, 
在 当前资金 w 的情况下,我们将所有 w >= capital[i]不同项目存储到大根堆中,
直到不满足 w >= capital[i]
若堆不为空则:
更新资金为w + pq.top(), 由于选择不同项目并删除堆顶元素,
若堆为空则:
说明当前资金无法完成任意项目, 跳出循环
typedef pair<int, int> pii;

class Solution {
public:
    int findMaximizedCapital(int k, int w, vector<int>& profits, vector<int>& capital) {
        int n = capital.size();
        int cur = 0;
        
        //优先队列,大根堆
        priority_queue<int, vector<int>, less<int>> pq;
        
        //记录项目与 对应资金
        vector<pii> arr;
        for (int i = 0; i < n; ++i){
            arr.push_back({capital[i], profits[i]});
        }
		//我们对第一成员capital进行排序
        sort(arr.begin(), arr.end());
        for (int i = 0; i < k; ++i){
            //当前资本w可以完成的项目,对应利润存储到大根堆中
            while(cur < n && arr[cur].first <= w){
                pq.push(arr[cur].second);
                ++cur;
            }
            //在堆不为空的条件下,拿到最大利润,即堆顶元素
            if (!pq.empty()){
                w += pq.top();
                pq.pop();
            }
            else {
            //任意项目都无法开展,结束选择
                break;
            }
        }
        return w;
    }
};

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值