日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

LeetCode算法题0:分发糖果【贪心算法】

發布時間:2025/6/17 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 LeetCode算法题0:分发糖果【贪心算法】 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 前言
  • 一、題目
  • 二、思路詳解
  • 三、搞點實際點兒的(C++實現)
    • 1.略顯粗糙的代碼實現
    • 2.稍顯精致的代碼實現
    • 3.最終的代碼實現
    • 4.提交結果
  • 總結


前言

??????本文記錄自己在LeetCode上關于一道算法題的解題過程,這道題花費了不少時間才通過。感覺在自己的這種解題風格道路上走遠了!但是代碼提交的結果還是比較滿意的。 哈哈,過程比較曲折,下面為大家一一道來:


一、題目

??????老師想給孩子們分發糖果,有 N 個孩子站成了一條直線,老師會根據每個孩子的表現,預先給他們評分。
??????你需要按照以下要求,幫助老師給這些孩子分發糖果:

??????要求1:每個孩子至少分配到 1 個糖果。
??????要求2:評分更高的孩子必須比他兩側的鄰位孩子獲得更多的糖果。

??????那么這樣下來,老師至少需要準備多少顆糖果呢?

示例1:

輸入:[1,0,2]
輸出:5
解釋:你可以分別給這三個孩子分發 2、1、2 顆糖果。

示例2:

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

該題所在網址:https://leetcode-cn.com/problems/candy/

二、思路詳解

??????瞧一瞧,看一看,大家品一下這個老師有多摳門~

??????首先:給每個孩子至少給一個糖果,這個簡單。要求 2 稍微難理解一點,參考下面這個示例來理解,概括為:如果一個孩子兩側,只要存在一個比他評分低的孩子,那么他的糖果數就要比這個低評分孩子的糖果要多。

這里給出一個示例:所有孩子的評分:{ 1,0,2,3,4,3,2,2 } 它對應的糖果數:{ 2,1,2,3,4,2,1,1 }。后面會對這個示例進一步分析。

??????最開始的思路是這樣的:按照平移的思想來設計,即;給起始第一個孩子1顆糖果,依次往后判斷,如果比它大則糖果加1,比它小則減1,碰到相等的情況則另做分析。 最后將整體結果往上平移(可能有負數存在),使得最小糖果數為1即可。 但是后來發現不可行,邏輯判斷太復雜,很難分析。 還有最重要的一點是,糖果數有時候會有一個突然的下降,比如給出的示例中評分為4和3的孩子,前者給4顆糖果,后者給2顆糖果。

??????所以單純的只從左到右的分析是不充分的,當然可以在這個基礎上再改改,比如補充上從右到左進行分析,不過在這兒不聊這個。聊點清新脫俗的~


————————————————??????華麗麗的分割線


??????就糖果數突然有一個下降的現象,我思考了一下原因。從而得到一種解題思路:

??????發現:如果能找到那些極小值點,也就是那些最終只分配 1 顆糖果的孩子(好慘!),因為給這些孩子的糖果數是已經確定的了。以此(這些糖果數)為基礎,再對這些孩子的左右分別進行分析,很容易就可以確定他們的左右孩子應得的糖果數,然后依次迭代計算擴展至所有孩子。 ???將整個評分序列想象成為一個函數,接下來要做的是找到其中的極小值點,給它們分配糖果,再對其余的評分序列繼續找當前的極小值點,分配糖果。直至分配結束。 ???想象一下快速排序的特點,每一次循環會確定一個最終排序結束時元素的位置。而目前的這個思路和它類似:每一次會確定若干個極小值點的糖果數。

??????所以具體的算法思路如下:
??????如果一個孩子的左右兩側評分都大于等于它,那么它就是一個極小值。
??????(1) 找到所有極小值點,將它們對應的糖果數量置為1
??????(2) 排除上述已經分配了糖果的孩子,在其余孩子中再次找極小值點,將它們對應的糖果數量置為2.(每一輪的極小值點分配的糖果數要比之前的多1個)
??????(3) 不斷重復步驟2. 直至給所有的孩子都分配了糖果。

??????對上面的示例進行分析:給定孩子的評分數組為 { 1,0,2,3,4,3,2,2 }
??????第一輪之后糖果數組為:_ 1 _ _ _ _ 1 1 ,其中第二個數 0 ,倒數第二個數 2 和最后一個數 2 為極小值。

??????第二輪在第一輪的基礎之上,得到兩個孩子的評分區間分別為:{ 1 } 和 { 2,3,4,3} 。對這兩個數組找它們的極小值,給這些極小值點的糖果數為2,得到第二輪之后糖果數組為:2 1 2 _ _ 2 1 1 ,其中區間 1 中唯一的一個數 1 是極小值,區間 2 中的第一個數 2 和最后一個數 3 為極小值。

??????第三輪在第二輪的基礎之上,得到一個孩子的評分區間分別為:{ 3,4} 。對這個數組找極小值,給這些極小值點的糖果數為3,得到第三輪之后糖果數組為:2 1 2 3 _ 2 1 1 ,其中第一個數 3 為最小值。

??????第四輪在第三輪的基礎之上,得到一個孩子的評分區間分別為:{ 4} 。對這個數組找極小值,給這些極小值點的糖果數為4,得到第四輪之后糖果數組為:2 1 2 3 4 2 1 1 ,這個唯一的數為極小值。

??????糖果數:{ 2,1,2,3,4,2,1,1 }即為最終結果。

三、搞點實際點兒的(C++實現)

1.略顯粗糙的代碼實現

??????說明:下面的代碼有助于對整體思路的把控,并且我在代碼中已經給出了詳細的注釋。缺點是太繁瑣,提交的時候會報超時,我稍后會在此之上做一些優化。
??????代碼如下:

class Solution { private:int count; //用來計數int start; //每一輪循環的起始下標int end;//每一輪循環的結束下標int assignCandy;//每一輪應該給當前極小值點的糖果數int sum; //求和,用來返回最終結果 public:int candy(vector<int>& ratings) {int N = ratings.size();//為幾個參數設置初始值count = 0;sum = 0;assignCandy = 1;start = 0;end = N - 1;int* flag = new int[N];//用來判斷哪些孩子已經被分配了糖果,若分配了就置為1int* candys = new int[N];//保存給每一個孩子分配的糖果數目memset(flag, 0, sizeof(int)*N);//將flag數組初始時全置為0setCandy(ratings, flag, candys);//第一輪分配,找到本輪的極小值點,并進行糖果分配//在第一輪分配之后,這里采用循環結構來進行每一輪的糖果分配,count表示當前已經分配了糖果的孩子數目,//所以退出條件這里寫作為 count<N while (count < N){assignCandy++;//每一輪待分配的糖果數量要比上一輪多1個for (int i = 0; i < N; i++)//遍歷一遍數組,以上一輪找到的極小值點為基礎,得到本輪應該分配糖果的孩子區間。{if (flag[i] == 0){start = i;while (i < N && flag[i] == 0)++i;end = i - 1;//每一次調用setCandy時,要先確定當前區間的start和end。setCandy(ratings, flag, candys); }}}for (int i = 0; i < N; i++)sum += candys[i]; //最后得到所有的糖果數目return sum;}void setCandy(vector<int>& temp, int flag[], int candys[]){if (start == end) //如果區間里只有一個數的情況,那它就是本輪的極小值,直接分配糖果并返回{candys[start] = assignCandy;flag[start] = 1;count++;return;}int i, prev, after;//判斷區間的第一個數是否為極小值,只需要和第二個數相比較即可。 按照之前分析的,注意這里需要取等號。if (temp[start + 1] >= temp[start]){candys[start] = assignCandy;flag[start] = 1;count++;}//判斷區間的最后一個數是否為極小值,只需要和倒數第二個數相比較即可。 按照之前分析的,注意這里需要取等號。if (temp[end] <= temp[end - 1]){candys[end] = assignCandy;flag[end] = 1;count++;}for (prev = start, i = start + 1, after = start + 2; i <= end - 1; ++i, ++after, ++prev){//對區間中間的所有數依次遍歷,判斷是否為極小值,按照之前分析的,注意這里需要取等號。if (temp[i] <= temp[prev] && temp[i] <= temp[after]){candys[i] = assignCandy;flag[i] = 1;count++;}}}};

??????需要指出一點,也是之前困擾了我比較久的一個問題。上面這個代碼,在while(count<N)循環中,有一行代碼我之前是寫成這樣的:

...while (flag[i] == 0 && i < N)++i;...

??????然后呢,它在VS2017中是可以編譯并運行的,但是在LeetCode中會報錯:“AddressSanitizer: heap-buffer-overflow on address…” ,個人猜測,在VS中對 && 應該是有做過優化的,而在LeetCode中是嚴格按照從左到右來判斷的,所以會出現數組訪問越界的現象。 解決方法:把 && 的左右互換一下就行。先判斷 i<N 就好了。


2.稍顯精致的代碼實現

??????上面這個代碼因為在提交時會報超時,所以需要進行邏輯上的刪減,去掉多余的部分。這次的代碼采用遞歸來實現,我依然會給出詳細的注釋。
代碼如下:

class Solution { private:int sum;//用來保存最終的糖果數,進行返回 public:int candy(vector<int>& ratings) {int N = ratings.size();sum = 0;int assignCandy = 1;//初始對每個極小值點分發的糖果數為1int start = 0; //第一輪循環的起始下標int end = N - 1; //第一輪循環的結束下標//這里就不需要 candys 和 flag 數組了哦setCandy(ratings, start, end, assignCandy); //遞歸方法調用return sum;}void setCandy(vector<int>& temp,int start,int end, int assignCandy){//注意:sum在 一旦找到當前輪次的極小值時,就會和當前的assignCandy相加,從而構成總的糖果數量。//如果區間長度為1的話,則它是一個極小值點,分配給它糖果,并將這個糖果數算在總數sum中。if (start == end){sum += assignCandy;return;}else if (start > end)//如果在兩個極小值接連出現時,會出現這種情況, 直接返回即可,因為兩個極小值之間沒有元素了return;int i, prev, after;int startT = start, endT; //設置新的區間開始節點startT//處理第一個數,如果它是極小值的話if (temp[start + 1] >= temp[start]){sum += assignCandy;startT = start + 1;}for (prev = start, i = start + 1, after = start + 2; i <= end-1 ; ++i, ++after, ++prev){if (temp[i] <= temp[prev] && temp[i] <= temp[after]){sum += assignCandy;//一旦碰到一個極小值點,首先需要更新一下區間結束節點endTendT = i - 1;//對一個區間進行遞歸方法調用,注意待分配的糖果數要加1setCandy(temp, startT, endT, assignCandy + 1);//注意:這里可能會發生startT大于endT的現象//還需要設置下一個區間的開始節點startTstartT = i + 1;}}//處理最后一個數,如果它是極小值的話if (temp[end] <= temp[end - 1]){sum += assignCandy;endT = end - 1;}elseendT = end;//對一次循環中的最后一個區間進行分析setCandy(temp, startT, endT, assignCandy + 1);} };

3.最終的代碼實現

??????2 中的代碼解決了 1 中存在的問題,但是它有一個缺點。
??????當測試用例為{20000,19999,19998,19997,…,4,3,2,1}時,這樣的逆序數組時,它的時間復雜度會急劇增大,是 2 中時間復雜度的 N 倍。所以,只好在 2 的基礎上加上了關于逆序的判斷;并且考慮到算法在完全升序排列的樣本上時間復雜度會增大,趨于O(N2),所以在加上關于升序的判斷,這點可以參考快速排序的缺點。
??????整體上代碼的時間復雜度為O(lnN),空間復雜度為O(lnN),性能都還可以。得到最終的代碼如下:

class Solution { private:int sum; public:int candy(vector<int>& ratings) {int N = ratings.size();sum = 0;int assignCandy = 1;int start = 0;int end = N - 1;setCandy(ratings, start, end, assignCandy);return sum;}void setCandy(vector<int>& temp,int start,int end, int assignCandy){if (start == end){sum += assignCandy;return;}else if (start > end)return;//如果區間中的元素是降序排列的,那么直接計算sum的值即可。 采用等差數列求和公式來計算。if (isSorted_descend(temp, start, end)) {int j = end - start; //j+1個數。int temp = (j + 1)*assignCandy + ((end-start)*(j + 1)) / 2;sum += temp;return;}else if (isSorted_ascend(temp, start, end)) //判斷是否為升序{int j = end - start; //j+1個數。int temp = (j + 1)*assignCandy + ((end-start)*(j + 1)) / 2;sum += temp;return;}int i, prev, after;int startT = start, endT;if (temp[start + 1] >= temp[start]){sum += assignCandy;startT = start + 1;}for (prev = start, i = start + 1, after = start + 2; i <= end-1 ; ++i, ++after, ++prev){if (temp[i] <= temp[prev] && temp[i] <= temp[after]){sum += assignCandy;endT = i - 1;setCandy(temp, startT, endT, assignCandy + 1);startT = i + 1;}}if (temp[end] <= temp[end - 1]){sum += assignCandy;endT = end - 1;}elseendT = end;setCandy(temp, startT, endT, assignCandy + 1);}//判斷當前區間的元素是否為降序排列。如果是,返回 1 如果兩個元素相同,則認為非降序//結果為真表示是一個嚴格的降序序列int isSorted_descend(vector<int>& temp, int start, int end){while (start < end){if (temp[start] <= temp[start + 1])return 0;start++;}return 1;}//對是否是一個嚴格的升序序列的判定int isSorted_ascend(vector<int>& temp, int start, int end){while (start < end){if (temp[start] >= temp[start + 1])return 0;start++;}return 1;}};

4.提交結果

??????以 3 中的代碼進行提交,結果如圖:

總結

??????最終的代碼實現整體上采用了遞歸的方法,也包含到了貪心和分治的思想。總算是按照自己的思路寫完了,挺費勁的,道路是曲折的,但是結局是美好的~

《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的LeetCode算法题0:分发糖果【贪心算法】的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。