1. 程式人生 > 其它 >演算法筆記--貪心

演算法筆記--貪心

技術標籤:力扣top100演算法貪心演算法

貪心

貪心演算法

貪心演算法是一種對某些求最優解問題的更簡單、更迅速的設計技術。
用貪心法設計演算法的特點是一步一步地進行,常以當前情況為基礎根據某個優化測度作最優選擇,而不考慮各種可能的整體情況,它省去了為找最優解要窮盡所有可能而必須耗費的大量時間,它採用自頂向下,以迭代的方法做出相繼的貪心選擇,每做一次貪心選擇就將所求問題簡化為一個規模更小的子問題, 通過每一步貪心選擇,可得到問題的一個最優解,雖然每一步上都要保證能獲得區域性最優解,但由此產生的全域性解有時不一定是最優的,所以貪婪法不要回溯。

貪婪演算法是一種改進了的分級處理方法,其核心是根據題意選取一種量度標準,然後將這多個輸入排成這種量度標準所要求的順序,按這種順序一次輸入一個量,如果這個輸入和當前已構成在這種量度意義下的部分最佳解加在一起不能產生一個可行解,則不把此輸入加到這部分解中。這種能夠得到某種量度意義下最優解的分級處理方法稱為貪婪演算法。
對於一個給定的問題,往往可能有好幾種量度標準。初看起來,這些量度標準似乎都是可取的,但實際上,用其中的大多數量度標準作貪婪處理所得到該量度意義下的最優解並不是問題的最優解,而是次優解。因此,選擇能產生問題最優解的最優量度標準是使用貪婪演算法的核心。
一般情況下,要選出最優量度標準並不是一件容易的事,但對某問題能選擇出最優量度標準後,用貪婪演算法求解則特別有效。


以下4道題為leetcode中幾道典型習題,貪心演算法在線上筆試中出現頻率較高,建議掌握。
除此之外還有一些兩地排程(#1029),檸檬水找零(#860)等問題都是貪心思想。

買賣股票的最佳時機

(#122 系列題型很愛考,可能是貪心、動態規劃)
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

設計一個演算法來計算你所能獲取的最大利潤。你可以儘可能地完成更多的交易(多次買賣一支股票)。

注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。

隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。

int maxProfit(vector<int>& prices) 
{
		if (prices.size() <= 1)
			return 0;

		int nMaxProfit = 0;//0 4 7 
		int nBuyPrice = prices[0];//  7 1   3   4
		int nSellPrice = prices[0];// 7 1 5 3 6 4
		for (vector<int>::size_type i = 1; i < prices.size(); ++i) {
			//如果第二天價格高  就賣  計算獲利
			if (prices[i] > nSellPrice) { //1-5  3-6   
				nMaxProfit += (prices[i] - nSellPrice);  //4 7
				// 更新賣出價格
				nSellPrice = prices[i];
			} 
			//如果第二天價格低了 就買
			else 
			{
				// 價格降低了,得重新買入了 7-1  5-3  6-4 
				nSellPrice = nBuyPrice = prices[i];
			}
		}

		return nMaxProfit;
	}

分糖果 (hard難度貪心)

老師想給孩子們分發糖果,有 N 個孩子站成了一條直線,老師會根據每個孩子的表現,預先給他們評分。

你需要按照以下要求,幫助老師給這些孩子分發糖果:
每個孩子至少分配到 1 個糖果。
相鄰的孩子中,評分高的孩子必須獲得更多的糖果。
那麼這樣下來,老師至少需要準備多少顆糖果呢?

示例 1:
輸入: [1,0,2]
輸出: 5
解釋: 你可以分別給這三個孩子分發 2、1、2 顆糖果。
示例 2:
輸入: [1,2,2]
輸出: 4
解釋: 你可以分別給這三個孩子分發 1、2、1 顆糖果。
第三個孩子只得到 1 顆糖果,這已滿足上述兩個條件。

方法1:遍歷兩次 滿足左規則和右規則

int candy(vector<int>& ratings) {
	const int N=ratings.size();
	if(N==0) return 0;
	if(N==1) return 1;
	vector<int> left(N,0);
	left[0]=1;
	for(int i=1;i<N;i++){
		if(ratings[i] > ratings[i-1]){
			left[i] = left[i-1] + 1;
		}else if(ratings[i] <= ratings[i-1]){
			left[i] = 1;
		}
	}
	int right = 1;
	int s = 0;
	s += max(left[N-1],right);
	for(int i=N-2;i>=0;i--){
		if(ratings[i] > ratings[i+1]){
			right = right + 1;
		}else if(ratings[i] <= ratings[i+1]){
			right = 1;
		}
		s += max(left[i],right);
	}
	
	return s;
}

方法2 :遍歷一遍

   int candy(vector<int>& ratings) {
        if(ratings.empty()) return 0;
        int size = ratings.size();
        int candies = 1;
        int sum = 1;
        int max = 0;
        int downIndex = 0;
        for(int i=1;i<size;i++){
            if(ratings[i]<ratings[i-1]){
                if(downIndex==0){
                    // 記錄上一個評分波峰峰值
                    max = candies;
                    // 記錄評分開始下降的位置
                    downIndex=i;
                }
                candies=1;
                // 補發糖果
                sum+=i-downIndex;
                // 判斷波峰是否需要補發糖果
                if(i-downIndex+1>=max)
                    sum+=1;
            }else if(ratings[i]>ratings[i-1]){
                downIndex = 0;
                candies+=1;
            }else{
                downIndex = 0;
                candies=1;
            }
            // 發糖果
            sum+=candies;
        }
        return sum;
    }

貪心判斷子序列

題目描述:給定字串 s 和 t ,判斷 s 是否為 t 的子序列。
你可以認為 s 和 t 中僅包含英文小寫字母。字串 t 可能會很長(長度 ~= 500,000),而 s 是個短字串(長度 <=100)。
字串的一個子序列是原始字串刪除一些(也可以不刪除)字元而不改變剩餘字元相對位置形成的新字串。(例如,"ace"是"abcde"的一個子序列,而"aec"不是)。
示例 1:
s = “abc”, t = “ahbgdc”
返回 true.
示例 2:
s = “axc”, t = “ahbgdc”
返回 false.

bool isSubsequence(string s, string t)
{
    if(s.empty())
        return true;
    if(t.empty())
        return false;
        
    int j =0;
    for(int i=0;i<t.size();i++)
    {
        if(j < s.size())
        {
            if(s[j] == t[i])
            {   
                j++;
            }                
        }  
    }
    if(j == s.size())
        return true;
	return false;
}

模擬行走機器人(leetcode#874)

機器人在一個無限大小的網格上行走,從點 (0, 0) 處開始出發,面向北方。該機器人可以接收以下三種類型的命令:
-2:向左轉 90 度
-1:向右轉 90 度
1 <= x <= 9:向前移動 x 個單位長度
在網格上有一些格子被視為障礙物。
第 i 個障礙物位於網格點 (obstacles[i][0], obstacles[i][1])
如果機器人試圖走到障礙物上方,那麼它將停留在障礙物的前一個網格方塊上,但仍然可以繼續該路線的其餘部分。
返回從原點到機器人的最大歐式距離的平方。
示例 1:
輸入: commands = [4,-1,3], obstacles = []
輸出: 25
解釋: 機器人將會到達 (3, 4)
示例 2:
輸入: commands = [4,-1,4,-2,4], obstacles = [[2,4]]
輸出: 65
解釋: 機器人在左轉走到 (1, 8) 之前將被困在 (1, 4) 處
設定一個方向,設定兩個方向陣列 一一對應 怎麼改變方向

i

nt robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
        int res = 0,x = 0, y = 0,dir = 0;   //dir 0 1 2 3
        int dx[] = {0,1,0,-1};//上  右  下  左
        int dy[] = {1,0,-1,0};
        set<pair<int,int>> s;   //使用set向量加快尋找速度
        for(int i=0;i<obstacles.size();i++)    //存障礙物
        {
            s.insert({obstacles[i][0],obstacles[i][1]}); 
        }
        for(int i=0;i<commands.size();i++)
        {
            if(commands[i] == -2)  //左轉  
                dir = (dir+3) % 4;
            else if(commands[i] == -1)   //右轉
                dir = (dir+1) % 4;
            else{
                for(int j=0;j<commands[i];j++)
                {
					//設定一個臨時座標
                    int tmp_x = x+dx[dir];
                    int tmp_y = y+dy[dir];
					//判斷是否有障礙物
                    if(s.find(make_pair(tmp_x,tmp_y)) == s.end())
                    {
						//沒有障礙物就相加
                        x = tmp_x;
                        y = tmp_y;
						//取最大值
                        res = max(res,x*x+y*y);
                    }
                }
            }
        }
        return res;
    }