1. 程式人生 > >演算法設計與分析 第七週 IPO

演算法設計與分析 第七週 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;
    }
};