Week8
Greedy
question source: IPO
question description
Suppose LeetCode will start its IPO soon. In order to sell a good price of its shares to Venture Capital, LeetCode would like to work on some projects to increase its capital before the IPO. Since it has limited resources, it can only finish at most k distinct projects before the IPO. Help LeetCode design the best way to maximize its total capital after finishing at most k distinct projects.
Given an array of non-negative integers, you are initially positioned at the first index of the array.You are given several projects. For each project i, it has a pure profit Pi and a minimum capital of Ci is needed to start the corresponding project. Initially, you have W capital. When you finish a project, you will obtain its pure profit and the profit will be added to your total capital.
To sum up, pick a list of at most k distinct projects from given projects to maximize your final capital, and output your final maximized capital.
Example 1:
Input: k=2, W=0, Profits=[1,2,3], Capital=[0,1,1].
Output: 4
Explanation: Since your initial capital is 0, you can only start the project indexed 0. After finishing it you will obtain profit 1 and your capital becomes 1. With capital 1, you can either start the project indexed 1 or the project indexed 2. Since you can choose at most 2 projects, you need to finish the project indexed 2 to get the maximum capital. Therefore, output the final maximized capital, which is 0 + 1 + 3 = 4.
Note:
- You may assume all numbers in the input are non-negative integers.
- The length of Profits array and Capital array will not exceed 50,000.
- The answer is guaranteed to fit in a 32-bit signed integer.
这是一道标签为贪心算法的题。这周继续学习了贪心算法的一些内容。这道题是说,公司要进行IPO(首次公开募股),要做项目来提高公司的总价值。给出若干个项目的启动金额和利润,给出你可以做的项目的个数和初始金额,求公司最大的总价值是多少。
解决方法
这很明显能用贪心算法来解决,每一次都选你能做的项目中的有最大利润的那一个项目。想法很简单朴素。
实现代码如下
class Solution {
public:
int findMaximizedCapital(int k, int W, vector<int>& Profits, vector<int>& Capital) {
int visited[Capital.size()];
memset(visited, 0, sizeof(visited));
for(int i = 0; i < k; i++){
//find the best profit
int best_profit = 0;
int best = -1;
for(int j = 0; j < Capital.size(); j++){
if(visited[j]) continue;
if(W >= Capital[j]){
if(Profits[j] >= best_profit){
best_profit = Profits[j];
best = j;
}
}
}
if(best == -1) break;
visited[best] = 1;
W += best_profit;
}
return W;
}
};
每次都遍历一次数组,找到能还没做过的项目中,目前资金能启动中收益最大的那个项目,重复k次。没错,错是没有错,只是不通过,Time Limit Exceeded,给出的样例是5000个项目,每次都要遍历一次数组,这太耗时间了。
于是就要改进算法了,就是要改进优先队列的实现方式,上面的方法是用一个无序数组来实现的。书上提过可以用二分堆的方法来实现。我就自己实现了一下堆结构。二分堆的元素在一个完全二叉树中。这是一棵最大堆:树中的任意节点的键值都必须大于等于其后裔的键值。所以树的根节点就一定是树的最大的元素。所以算法就是维护一个二分堆,二分堆保存的是可做项目的利润,每次取根节点作为此次要做的项目,做完后,再把能做的项目放进堆里维护。
实现代码如下
int findMaximizedCapital(int k, int W, vector<int>& Profits, vector<int>& Capital) {
for(int i = 0; i < Capital.size(); i++){
pair<int, int> p(Capital[i], Profits[i]);
v.push_back(p);
}
// 根据启动金额排序
sort(v.begin(), v.end());
int pos = -1;
for(int i = 0; i < k; i++){
for(int j = pos + 1; j < v.size(); j++){
if(W >= v[j].first){
insert(heap, v[j].second);
pos = j;
}else{
break;
}
}
if(heap.empty()) break;
W += heap[0];
deletemax(heap);
}
return W;
}
进行insert操作,将新元素放在最底端(最底端最先可用的位置),然后 “冒泡” :如果新元素比其父节点要大,则将新元素与其父节点交换,并重复其过程。交换的次数最多为树的高度。位置为j的节点,父节点为j/2。(1 <= j <= size)。
void insert(vector<int>& heap, int n){
heap.push_back(n);
//bubble
int i = heap.size();
if(i == 1) return;
while(heap[i - 1] > heap[i / 2 - 1]){
int tem = heap[i - 1];
heap[i - 1] = heap[i / 2 - 1];
heap[i / 2 - 1] = tem;
i /= 2;
if(i == 1 || i == 0) break;
}
}
deletemax操作,直接返回根节点的键值,将根节点对应元素从堆中删除之后,取出树中的最后一个节点,将它置于树根上,再令它 “向下过滤” :如果节点的键值小于它的任何一个子节点,则将它与较大的子节点进行交换,并重复此过程。也是最多交换次数为树的高度。位置为j的子节点的位置分别为2j和2j+1。(1 <= j <= size)
void deletemax(vector<int>& heap){
int size = heap.size();
if(size == 0) return;
if(size == 1) {
heap.pop_back();
return;
}
heap[0] = heap.back();
heap.pop_back();
size--;
//sink
int i = 1;
while(true){
//no kid
if(2 * i > size) break;
//only have left
else if(2 * i <= size && 2 * i + 1 > size){
if(heap[2 * i - 1] > heap[i - 1]){
int tem = heap[2 * i - 1];
heap[2 * i - 1] = heap[i - 1];
heap[i - 1] = tem;
i = 2 * i;
}else{
break;
}
}
//have left and right
else{
//swap left
if(heap[2 * i - 1] > heap[i - 1] && heap[2 * i - 1] >= heap[2 * i]){
int tem = heap[2 * i - 1];
heap[2 * i - 1] = heap[i - 1];
heap[i - 1] = tem;
i = 2 * i;
}
//swap right
else if(heap[2 * i] > heap[i - 1] && heap[2 * i] >= heap[2 * i - 1]){
int tem = heap[2 * i];
heap[2 * i] = heap[i - 1];
heap[i - 1] = tem;
i = 2 * i + 1;
}
else{
break;
}
}
}
}
实现的思路是很清晰的,但是在真正实现的时候就会有很多问题。尤其是数组越界问题,以至于要加很多判断条件。现在自己实现一次就清晰很多了。
C++里面有一种STL容器叫priority_queue,用它就很方便,底层实现也是用堆。但我是想自己用堆来实现一个,加深一下印象。