1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        一舉拿下貪心

        共 19301字,需瀏覽 39分鐘

         ·

        2021-05-14 03:56

        大家好,我是Simon郎,一個每天想要博學一點點的小青年!

        今天為大家分享的是貪心算法。

        貪心算法:貪心算法(又稱貪婪算法)是指,在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優(yōu)上加以考慮,算法得到的是在某種意義上的局部最優(yōu)解,從而使得得到的最終結果是全局最優(yōu)或者接近于全局最優(yōu)。

        本文選取LeetCode比較有代表性的題目來學習貪心算法,選取的題目如下:

        • 1、分發(fā)餅干(455)

        • 2、分發(fā)糖果(135)

        • 3、無重疊區(qū)間(435)

        • 4、種花問題(605)

        • 5、用最少數(shù)量的箭引爆氣球(452)

        • 6、劃分字母區(qū)間(763)

        • 7、買賣股票的最佳時機(122)

        • 8、根據(jù)身高重建隊列(406)

        • 9、非遞減數(shù)列(665)

        每道題目都有五部分組成:

        • 題目描述
        • 示例
        • 解題思路
        • 代碼實現(xiàn)
        • 執(zhí)行結果

        1、分發(fā)餅干(455)

        題目描述

        假設你是一位很棒的家長,想要給你的孩子們一些小餅干。但是,每個孩子最多只能給一塊餅干。

        對每個孩子 i,都有一個胃口值 g[i],這是能讓孩子們滿足胃口的餅干的最小尺寸;并且每塊餅干 j,都有一個尺寸 s[j] 。如果 s[j] >= g[i],我們可以將這個餅干 j 分配給孩子 i ,這個孩子會得到滿足。你的目標是盡可能滿足越多數(shù)量的孩子,并輸出這個最大數(shù)值。

        示例:

        示例1:

        輸入: g = [1,2,3], s = [1,1] 

        輸出: 1 

        解釋: 你有三個孩子和兩塊小餅干,3個孩子的胃口值分別是:1,2,3。雖然你有兩塊小餅干,由于他們的尺寸都是1,你只能讓胃口值是1的孩子滿足。所以你應該輸出1

        示例2:

        輸入: g = [1,2], s = [1,2,3] 

        輸出: 2 

        解釋: 你有兩個孩子和三塊小餅干,2個孩子的胃口值分別是1,2。你擁有的餅干數(shù)量和尺寸都足以讓所有孩子滿足。所以你應該輸出2.

        思路:

        采用貪心算法:大尺寸的餅干能夠滿足胃口小的孩子,也能夠滿足胃口大的孩子,所以對于大尺寸的餅干要優(yōu)先滿足胃口大的孩子,而胃口小的孩子用小尺寸的餅干來喂,盡量減少浪費。

        局部最優(yōu):充分利用餅干尺寸滿足一個;全局最優(yōu):就是滿足盡可能多的孩子

        首先將餅干數(shù)組和孩子數(shù)組進行排序,然后從前往后依次進行比較。

        胃口比餅干尺寸小,孩子被滿足,繼續(xù)遍歷下一個孩子和下一塊餅干; 胃口比餅干尺寸大,遍歷下一塊餅干。

        代碼:

        class Solution {
            public int findContentChildren(int[] g, int[] s) {
                Arrays.sort(g);
                Arrays.sort(s);
                int child=0, cookie=0;
                while(child<g.length && cookie<s.length){
                    if(g[child]<=s[cookie]) child++;
                    cookie++;
                }
             return child;
            }
        }

        執(zhí)行結果:

        分發(fā)餅干

        2、分發(fā)糖果(135)

        題目描述

        老師想給孩子們分發(fā)糖果,有 N 個孩子站成了一條直線,老師會根據(jù)每個孩子的表現(xiàn),預先給他們評分。要按照以下要求,幫助老師給這些孩子分發(fā)糖果:每個孩子至少分配到 1 個糖果。評分更高的孩子必須比他兩側的鄰位孩子獲得更多的糖果。

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

        示例

        示例 1:

        輸入:[1,0,2] 

        輸出:5 

        解釋:你可以分別給這三個孩子分發(fā) 2、1、2 顆糖果。

        示例 2:

        輸入:[1,2,2] 

        輸出:4 

        解釋:你可以分別給這三個孩子分發(fā) 1、2、1 顆糖果。第三個孩子只得到 1 顆糖果,這已滿足上述兩個條件。

        解題思路:

        相鄰孩子,評分高的孩子必須獲得更多的糖果,因此拆分成兩個規(guī)則:

        • 規(guī)則定義:設學生A和學生B左右相鄰,A在B的左邊

        左規(guī)則:當B>A時,B的糖果比A的數(shù)量多

        右規(guī)則:當A>B時,則A的糖果比B的糖果數(shù)量多

        相鄰的學生中,評分高的學生必須獲得更多的糖果 等價于 所有學生滿足左規(guī)則且滿足右規(guī)則。

        • 算法流程
        • 把所有孩子的糖果數(shù)初始化為 1。
        • 先從左往右遍歷一遍,如果右邊孩子的評分比左邊的高,則右邊孩子的糖果數(shù)更新為左邊孩子的 糖果數(shù)加 1。
        • 再從右往左遍歷一遍,如果左邊孩子的評分比右邊的高,且左邊孩子當前的糖果數(shù) 不大于右邊孩子的糖果數(shù),則左邊孩子的糖果數(shù)更新為右邊孩子的糖果數(shù)加 1。

        代碼:

        class Solution {
            public int candy(int[] ratings) {
                //循環(huán)遍歷兩次
                int[] candy=new int[ratings.length];
                int count=candy.length;
                for(int i=1;i<ratings.length;i++){
                    if(ratings[i-1]<ratings[i]){
                        candy[i]=candy[i-1]+1;
                    }
                }  
                for(int j=ratings.length-1;j>0;j--){
                    if(ratings[j]<ratings[j-1]){
                        candy[j-1]=Math.max(candy[j-1],candy[j]+1);
                    }
                }
                for(int k=0;k<candy.length;k++){
                    count+=candy[k];
                }
                return count;
            }
        }

        執(zhí)行結果:

        分發(fā)糖果

        3、無重疊區(qū)間(435)

        題目描述:

        給定一個區(qū)間的集合,找到需要移除區(qū)間的最小數(shù)量,使剩余區(qū)間互不重疊。

        注意:可以認為區(qū)間的終點總是大于它的起點;區(qū)間[1,2]和[2,3]的邊界相互接觸,但沒有相互重疊

        示例:

        示例 1:

        輸入: [ [1,2], [2,3], [3,4], [1,3] ]

        輸出: 1

        解釋: 移除 [1,3] 后,剩下的區(qū)間沒有重疊。

        示例 2:

        輸入: [ [1,2], [1,2], [1,2] ]

        輸出: 2

        解釋: 你需要移除兩個 [1,2] 來使剩下的區(qū)間沒有重疊。

        示例 3:

        輸入: [ [1,2], [2,3] ]

        輸出: 0

        解釋: 你不需要移除任何區(qū)間,因為它們已經是無重疊的了。

        解題思路:

        首先要對區(qū)間進行排序,本題采用的是對區(qū)間的頭元素進行排序,然后在遍歷空間

        • 如果后面區(qū)間的頭小于當前區(qū)間的尾,比如當前區(qū)間是[3,6],后面區(qū)間是[4,5]或者是[5,9],說明這兩個區(qū)間有重復,必須要移除一個,那么要移除哪個呢,為了防止在下一個區(qū)間和現(xiàn)有區(qū)間有重疊,我們應該讓現(xiàn)有區(qū)間越短越好,所以應該移除尾部比較大的,保留尾部比較小的。
        • 如果后面區(qū)間的頭不小于當前區(qū)間的尾,說明他們沒有重合,不需要移除

        代碼:

        class Solution {
            public int eraseOverlapIntervals(int[][] intervals) {
                //對2*2數(shù)組按照左邊界排序
                Arrays.sort(intervals,(a,b)->a[0]-b[0]);
                int end=intervals[0][1];
                int count=0;
                for(int i=1;i<intervals.length;i++){
                    if(intervals[i][0]<end){
                        //區(qū)間有重疊,選取右邊界最小的數(shù)組
                        end=Math.min(end,intervals[i][1]);
                        count++;
                    }else{
                        //區(qū)間無重疊,直接更新就好
                        end=intervals[i][1];
                    }
                  
                }
                return count;
            }
        }

        執(zhí)行結果:

        無重疊區(qū)

        4、種花問題(605)

        題目描述:

        假設有一個很長的花壇,一部分地塊種植了花,另一部分卻沒有??墒?,花不能種植在相鄰的地塊上,它們會爭奪水源,兩者都會死去。

        給你一個整數(shù)數(shù)組  flowerbed 表示花壇,由若干 0 和 1 組成,其中 0 表示沒種植花,1 表示種植了花。另有一個數(shù) n ,能否在不打破種植規(guī)則的情況下種入 n 朵花?能則返回 true ,不能則返回 false

        示例

        示例 1:

        輸入:flowerbed = [1,0,0,0,1], n = 1 輸出:true

        示例 2:

        輸入:flowerbed = [1,0,0,0,1], n = 2 輸出:false

        解題思路:

        題目要求是否能在不打破規(guī)則的情況下插入n朵花,與直接計算不同,采用“跳格子”的解法只需遍歷不到一遍數(shù)組,處理以下兩種不同的情況即可:

        • 當遍歷到index遇到1時,說明這個位置有花,那必然從index+2的位置才有可能種花,因此當碰到1時直接跳過下一格。
        • 當遍歷到index遇到0時,由于每次碰到1都是跳兩格,因此前一格必定是0,此時只需要判斷下一格是不是1即可得出index這一格能不能種花,如果能種則令n減一,然后這個位置就按照遇到1時處理,即跳兩格;如果index的后一格是1,說明這個位置不能種花且之后兩格也不可能種花,直接跳過3格。

        當n減為0時,說明可以種入n朵花,則可以直接退出遍歷返回true;如果遍歷結束n沒有減到0,說明最多種入的花的數(shù)量小于n,則返回false。

        代碼:

        class Solution {
            public boolean canPlaceFlowers(int[] flowerbed, int n) {
                 //采用跳格子的思想解決
                 //1,index為1,跳兩格
                 //2 index為0,證明前一個格子是0,只需要判斷下一個格子是否為0
                 //若為0,則n-1,
                 //若為1,則跳三格
                 for(int i=0;i<flowerbed.length && n>0;){
                     if(flowerbed[i]==1){
                         i=i+2;
                         //一定要注意邊界問題
                     } else if(i==flowerbed.length-1 || flowerbed[i+1]==0){
                            n--;
                            i=i+2;
                     }else{
                            i=i+3;
                        }
                 }
                 return n<=0;
            }
        }

        執(zhí)行結果:

        種花問題

        5、用最少數(shù)量的箭引爆氣球(452)

        題目描述:

        在二維空間中有許多球形的氣球。對于每個氣球,提供的輸入是水平方向上,氣球直徑的開始和結束坐標。由于它是水平的,所以縱坐標并不重要,因此只要知道開始和結束的橫坐標就足夠了。開始坐標總是小于結束坐標。

        一支弓箭可以沿著 x 軸從不同點完全垂直地射出。在坐標 x 處射出一支箭,若有一個氣球的直徑的開始和結束坐標為 xstart,xend, 且滿足  xstart ≤ x ≤ xend,則該氣球會被引爆??梢陨涑龅墓臄?shù)量沒有限制。弓箭一旦被射出之后,可以無限地前進。我們想找到使得所有氣球全部被引爆,所需的弓箭的最小數(shù)量。

        給你一個數(shù)組 points ,其中 points [i] = [xstart,xend] ,返回引爆所有氣球所必須射出的最小弓箭數(shù)。

        示例

        示例 1:

        輸入:points = [[10,16],[2,8],[1,6],[7,12]] 輸出:2 解釋:對于該樣例,x = 6 可以射爆 [2,8],[1,6] 兩個氣球,以及 x = 11 射爆另外兩個氣球

        示例 2:

        輸入:points = [[1,2],[3,4],[5,6],[7,8]] 輸出:4

        示例 3:

        輸入:points = [[1,2],[2,3],[3,4],[4,5]] 輸出:2

        示例 4:

        輸入:points = [[1,2]] 輸出:1

        示例 5:

        輸入:points = [[2,3],[2,3]] 輸出:1

        解題思路:

        首先想到的思路是對數(shù)組中的區(qū)間值按左端點進行排序

        • 如果新氣球的左端點大于射擊區(qū)間的右邊界,那么我們就需要重新開辟一個區(qū)間;

        • 如果新氣球的左端點小于射擊區(qū)間的右邊界,這里又要分為兩種情況:如果右端點大于射擊區(qū)間的右邊界,那么我們的射擊區(qū)間的右邊界無需變化;如果右端點小于射擊區(qū)間的右邊界,那么我們射擊區(qū)間的右邊界就要向左移動,以新氣球的右端點為準,確立新的邊界。

        代碼:

        class Solution {
            public int findMinArrowShots(int[][] points) {
            //貪心算法,找出一支箭引爆最多的氣球的
                if(points.length==0return 0;
            //對二維數(shù)組按照左邊界從小到大排休排序
            Arrays.sort(points,(a,b)->a[0]<b[0]?-1:1);
            int right=points[0][1];
            int count=1;
            for(int i=1;i<points.length;i++){
                if(points[i][0]>right){
                    count++;
                    right=points[i][1];
                }
                else{
                    if(points[i][1]<right){
                        right=points[i][1];
                    }
                }
            } 
            return count;
            }
        }

        執(zhí)行結果:

        用最少數(shù)量的箭引爆氣球

        6、劃分字母區(qū)間(763)

        題目描述:

        字符串 S 由小寫字母組成。我們要把這個字符串劃分為盡可能多的片段,同一字母最多出現(xiàn)在一個片段中。返回一個表示每個字符串片段的長度的列表。

        示例:

        輸入:S = "ababcbacadefegdehijhklij" 

        輸出:[9,7,8]

        解釋:劃分結果為 "ababcbaca", "defegde", "hijhklij"。每個字母最多出現(xiàn)在一個片段中。像 "ababcbacadefegde", "hijhklij" 的劃分是錯誤的,因為劃分的片段數(shù)較少。

        解題思路:

        由于同一個字母只能出現(xiàn)在同一個片段,顯然同一個字母的第一次出現(xiàn)的下標位置和最后一次出現(xiàn)的下標位置必須出現(xiàn)在同一個片段。因此需要遍歷字符串,得到每個字母最后一次出現(xiàn)的下標位置。

        在得到每個字母最后一次出現(xiàn)的下標位置之后,可以使用貪心的方法將字符串劃分為盡可能多的片段,具體做法如下。

        • 從左到右遍歷字符串,遍歷的同時維護當前片段的開始下標start和結束下標end,初始化start=end=0。
        • 對于每個訪問到的字母,得到當前字母的最后一次出現(xiàn)的下標位置end1,則當前片段的結束下標一定不會小于end1,因此,令end=max(end,end1)
        • 當訪問到下標end 時,當前片段訪問結束,當前片段的下標范圍是 [start,end],長度為end?start+1,將當前片段的長度添加到返回值,然后令 start=end+1,繼續(xù)尋找下一個片段
        • 重復上述過程,直至遍歷完字符串

        代碼:

        class Solution {
            public List<Integer> partitionLabels(String s) {
                //先生成一個長度為26的數(shù)組,用于存放每個的不同字符在序列中的最終位置
                int[] alphabet=new int[26];
                List list=new ArrayList<>();
                int start=0, end=0;
                for(int i=0;i<s.length();i++){
                    alphabet[s.charAt(i)-'a']=i;
                }

                for(int i=0;i<s.length();i++){
                    end=Math.max(end,alphabet[s.charAt(i)-'a']);
                    if(end==i){
                        list.add(end-start+1);
                        start=end+1;
                    }
                }
                return list;
            }
        }

        執(zhí)行結果:

        劃分字母區(qū)間

        7、買賣股票的最佳時機(122)

        題目描述:

        給定一個數(shù)組 prices ,其中 prices[i] 是一支給定股票第 i 天的價格。設計一個算法來計算你所能獲取的最大利潤。你可以盡可能地完成更多的交易(多次買賣一支股票)。

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

        示例:

        示例 1:

        輸入: prices = [7,1,5,3,6,4] 

        輸出: 7 

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

        示例 2:

        輸入: prices = [1,2,3,4,5] 

        輸出: 4

        解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接連購買股票,之后再將它們賣出。因為這樣屬于同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。 

        示例 3:

        輸入: prices = [7,6,4,3,1]

        輸出: 0 

        解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。

        解題思路:

        • 由于該題可以買賣無限次,只要第二天的價格大于前一天的價格我們就買前一天的股票,然后在第二天賣掉,這樣我們就能從中獲取利潤;
        • 反之,如果第二天的價格小于前一天的價格我們就不進行買賣。

        代碼:

        class Solution {
            public int maxProfit(int[] prices) {
                //
                int sum=0;
                int start=0, end=0;
                for(int i=1;i<prices.length;i++){
                    if(prices[i]-prices[i-1]>0){
                        sum+=prices[i]-prices[i-1];
                    }
                }
                return sum;
            }
        }

        執(zhí)行結果

        買賣股票的最佳時機

        8、根據(jù)身高重建隊列(406)

        題目描述:

        假設有打亂順序的一群人站成一個隊列,數(shù)組 people 表示隊列中一些人的屬性(不一定按順序)。每個 people[i] = [hi, ki] 表示第 i 個人的身高為 hi ,前面 正好 有 ki 個身高大于或等于 hi 的人。

        請你重新構造并返回輸入數(shù)組 people 所表示的隊列。返回的隊列應該格式化為數(shù)組 queue ,其中 queue[j] = [hj, kj] 是隊列中第 j 個人的屬性(queue[0] 是排在隊列前面的人)。

        示例:

        示例 1:

        輸入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]] 

        輸出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 

        解釋:編號為 0 的人身高為 5 ,沒有身高更高或者相同的人排在他前面。編號為 1 的人身高為 7 ,沒有身高更高或者相同的人排在他前面。編號為 2 的人身高為 5 ,有 2 個身高更高或者相同的人排在他前面,即編號為 0 和 1 的人。編號為 3 的人身高為 6 ,有 1 個身高更高或者相同的人排在他前面,即編號為 1 的人。編號為 4 的人身高為 4 ,有 4 個身高更高或者相同的人排在他前面,即編號為 0、1、2、3 的人。編號為 5 的人身高為 7 ,有 1 個身高更高或者相同的人排在他前面,即編號為 1 的人。因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新構造后的隊列。

        示例 2:

        輸入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]] 

        輸出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]

        解題思路:

        一般這種數(shù)對,還涉及排序的,根據(jù)第一個元素正向排序,根據(jù)第二個元素反向排序,或者根據(jù)第一個元素反向排序,根據(jù)第二個元素正向排序,往往能夠簡化解題過程。

        在本題目中,首先對數(shù)對進行排序,按照數(shù)對的元素 1 降序排序,按照數(shù)對的元素 2 升序排序。

        原因是:按照元素 1 進行降序排序,對于每個元素,在其之前的元素的個數(shù),就是大于等于他的元素的數(shù)量,而按照第二個元素正向排序,我們希望 k 大的盡量在后面,減少插入操作的次數(shù)。

        代碼:

        class Solution {
            public int[][] reconstructQueue(int[][] people) {
              // [7,0], [7,1], [6,1], [5,0], [5,2], [4,4]
                // 再一個一個插入。
                // [7,0]
                // [7,0], [7,1]
                // [7,0], [6,1], [7,1]
                // [5,0], [7,0], [6,1], [7,1]
                // [5,0], [7,0], [5,2], [6,1], [7,1]
                // [5,0], [7,0], [5,2], [6,1], [4,4], [7,1]
                Arrays.sort(people, (o1, o2) -> o1[0] == o2[0] ? o1[1] - o2[1] : o2[0] - o1[0]);

                LinkedList<int[]> list = new LinkedList<>();
                for (int[] i : people) {
                    list.add(i[1], i);
                }

                return list.toArray(new int[list.size()][2]);
            }
        }

        執(zhí)行結果

        根據(jù)身高重建隊列

        9、非遞減數(shù)列(665)

        題目描述:

        給你一個長度為 n 的整數(shù)數(shù)組,請你判斷在 最多 改變 1 個元素的情況下,該數(shù)組能否變成一個非遞減數(shù)列。

        我們是這樣定義一個非遞減數(shù)列的:對于數(shù)組中任意的 i (0 <= i <= n-2),總滿足 nums[i] <= nums[i + 1]。

        示例:

        示例 1:

        輸入: nums = [4,2,3] 輸出: true 解釋: 你可以通過把第一個4變成1來使得它成為一個非遞減數(shù)列。

        示例 2:

        輸入: nums = [4,2,1] 輸出: false 解釋: 你不能在只改變一個元素的情況下將其變?yōu)榉沁f減數(shù)列。

        解題思路:

        本題是要維持一個非遞減的數(shù)列,所以遇到遞減的情況時(nums[i] > nums[i + 1]),要么將前面的元素縮小,要么將后面的元素放大。

        但是本題唯一的易錯點就在這,

        如果將nums[i]縮小,可能會導致其無法融入前面已經遍歷過的非遞減子數(shù)列;如果將nums[i + 1]放大,可能會導致其后續(xù)的繼續(xù)出現(xiàn)遞減;所以要采取貪心的策略,在遍歷時,每次需要看連續(xù)的三個元素,也就是瞻前顧后,遵循以下兩個原則:

        需要盡可能不放大nums[i + 1],這樣會讓后續(xù)非遞減更困難;如果縮小nums[i],但不破壞前面的子序列的非遞減性;算法步驟:

        遍歷數(shù)組,如果遇到遞減:還能修改:

        修改方案1:將nums[i]縮小至nums[i + 1]

        修改方案2:將nums[i + 1]放大至nums[i];不能修改了:直接返回false

        代碼:

        class Solution {
            public boolean checkPossibility(int[] nums) {
                if(nums.length==1return true;
                boolean flag=nums[0]<=nums[1]?true:false;
                for(int i=1;i<nums.length-1;i++){
                    if(nums[i+1]<nums[i]){
                        if(flag){
                            if(nums[i+1]>=nums[i-1])
                                nums[i]=nums[i+1];
                            else
                                nums[i+1]=nums[i];
                            flag=false;
                        }
                        else 
                           return false;
                    }
                }
                return true;
            }
        }

        執(zhí)行結果

        非遞減數(shù)列

        瀏覽 190
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            丁香久久 | 美女黄色免费在线观看 | 成人AV在线播放 | 高跟鞋少妇色情A片 | 少妇69xx | 亚洲视频导航 | 极品国产91在线网站 | 在线观看欧美日韩 | 女人扒开尿口给男人捅 | 亚州AV久久精品美女模特图片 |