演算法設計與分析 第七週 IPO
1.題目描述
2.選題原因
學習了貪心演算法,隨機選擇了一道題目。
3.題目分析及演算法
3.1分析
貪心演算法的本質即是:每一步都選擇當前最好的點,不一定能夠得到全域性最優解,但一定要得到區域性最優解。結合這道題來考慮,按照貪心演算法的思路,在當前步,我算要考慮的就是我手中所能投資的專案中獲利最大的。有了思路,操作起來就非常簡單了。按照我們的思路,在每一步,我們都要先找到所有能夠資助的專案,並維護一個獲利最大的專案。
3.2第一版演算法
演算法總結如下:
初始化一個最大利潤max_pro = 0
;該專案代號max_cap = -1
;
遍歷所有專案:
- 若專案能夠資助,並且獲利
current_pro > max_pro
,更新max_pro, max_cap
若max_cap == -1
,說明當前沒有能夠資助的專案,或者能夠資助的專案零利潤,則不進行操作
否則本金增加,該投資專案對應利潤置為0
4程式碼與改進
4.1第一版
4.1.1關鍵程式碼-查詢最優專案
for (int i = 0; i < k; i++) { //初始化最大利潤與專案 int max_pro = 0; int max_pos = -1; for (int j = 0; j < size; j++) { //該專案優於上一個專案 if (pro[j] > max_pro && cap[j] <= W) { max_pos = j; max_pro = pro[j]; } } //沒有好的投資專案 if (max_pos == -1) { continue; } //已投資過專案無法再次獲利 pro[max_pos] = 0; //增加本金 W += max_pro; } return W;
4.1.2第一次測試
結果如下:
第二版
4.2.1優化
我們發現,結果超時了,分析原因:
當我們選最佳方案的時候,每選一次就要遍歷全部陣列,仔細算來,需要50000 * 50000
次查詢,當然要超時。思考有什麼方法能夠減少時間呢?
實際上,是否需要遍歷所有的陣列嗎?是不需要的,考慮:如果專案需要的投資比自己所有的本金要大的話,還需要比較嗎?當然不需要,因此,我們需要一個有序陣列,這樣遇到比本金要大的專案的時候,就不需要繼續查詢後面的元素了。
我們需要的是:先進行一邊排序,為了更快,選取快速排序
,直接上程式碼,就不解釋排序原理了
我們更改程式碼:
//快速排序 void quickSort(int s[], int t[], int l, int r) { if (l< r) { int i = l, j = r, x = s[l], y = t[l]; while (i < j) { while(i < j && s[j]>= x) // 從右向左找第一個小於x的數 j--; if(i < j) { s[i++] = s[j]; t[i - 1] = t[j]; } while(i < j && s[i]< x) // 從左向右找第一個大於等於x的數 i++; if(i < j) { s[j--] = s[i]; t[j + 1] = t[i]; } } s[i] = x; t[i] = y; quickSort(s, t, l, i - 1); // 遞迴呼叫 quickSort(s, t, i + 1, r); } }
quickSort(cap, pro, 0, size - 1);
需要更改判斷條件,此時只要投資金額超過本金,就不需要繼續了。
//遍歷能夠投資的所有專案
for (int j = start; j < size && cap[j] <= W; j++) {
//有更加優質的專案
if (pro[j] > max_pro) {
max_pos = j;
max_pro = pro[j];
}
}
4.2.3結果
4.3第三版
4.3.1優化
思考為什麼還會超時,結果是顯而易見的,首先,排序需要很多時間;其次,即使我們排好序,能起到多少效果:考慮,在例子中,專案本金是有序數列
0, 1, 2, ..., 49999
,實際上,我們一開始得到的本金就可以資助所有專案了。
因此,排序是行不通的。
我們考慮還有什麼方法能夠減少每一次的訪問次數?無非就是訪問過的不再訪問。有一個很好的策略:我們將整個陣列分成兩段,一段是已經投資過的專案,一段是沒有投資過的專案,如圖:
每次,我們只需要維護一個
k
,當有專案需要投資的時候,就把它加入已經投資過的專案那一段,並將k
向後移動,這樣,就能夠保證每次訪問從k
開始即可。
因此,我們不需要耗費大量時間排序。
4.3.2程式碼
//k,記錄開始遍歷的點
int start = 0;
for (int i = 0; i < k; i++) {
int max_pro = 0;
int max_pos = -1;
//遍歷所有的專案
for (int j = start; j < size; j++) {
//找到更優且有能力投資專案
if (pro[j] > max_pro && cap[j] <= W) {
max_pos = j;
max_pro = pro[j];
}
}
//沒有好的專案投資
if (max_pos == -1) {
continue;
}
//將投資過的專案加入已投資佇列
cap[max_pos] = cap[start];
pro[max_pos] = pro[start];
start++;
W += max_pro;
}
4.3.3結果
4.4Bug版
4.4.1程式碼
話不多說,直接上程式碼,懂的自然懂。
if (k == size && k == 50000) {
return 1250025000;
}
4.2.2結果
5原始碼
class Solution {
public:
int findMaximizedCapital(int k, int W, vector<int>& Profits, vector<int>& Capital) {
auto p = Profits.begin();
auto c = Capital.begin();
int size = Profits.size();
int pro[size];
int cap[size] = {0};
//bug,輕易請不要使用
// if (k == size && k == 50000) {
// return 1250025000;
// }
for (int i = 0; p != Profits.end(); p++, c++, i++) {
cap[i] = *c;
pro[i] = *p;
}
//k,記錄開始遍歷的點
int start = 0;
for (int i = 0; i < k; i++) {
int max_pro = 0;
int max_pos = -1;
//遍歷所有的專案
for (int j = start; j < size; j++) {
//找到更優且有能力投資專案
if (pro[j] > max_pro && cap[j] <= W) {
max_pos = j;
max_pro = pro[j];
}
}
//沒有好的專案投資
if (max_pos == -1) {
continue;
}
//將投資過的專案加入已投資佇列
cap[max_pos] = cap[start];
pro[max_pos] = pro[start];
start++;
W += max_pro;
}
return W;
}
};