貪心算法及LeetCode中相關問題

最近在複習軟考,有十天左右沒有刷題,感覺真是罪過?(沒有保持住一天一題的習慣哈哈),開始繼續刷題,學習貪心算法思想,並在LeetCode上進行相關tag的刷題?

定義:

貪心算法(又稱貪婪算法)是指,在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,他所做出的是在某種意義上的局部最優解。

貪心算法不是對所有問題都能得到整體最優解,關鍵是貪心策略的選擇,選擇的貪心策略必須具備無後效性,即某個狀態以前的過程不會影響以後的狀態,只與當前狀態有關。

貪心策略適用的前提是:局部最優策略能導致產生全局最優解。

455. Assign Cookies

題目描述:

假設你是一位很棒的家長,想要給你的孩子們一些小餅乾。但是,每個孩子最多隻能給一塊餅乾。對每個孩子 i ,都有一個胃口值 gi ,這是能讓孩子們滿足胃口的餅乾的最小尺寸;並且每塊餅乾 j ,都有一個尺寸 sj 。如果 sj >= gi ,我們可以將這個餅乾 j 分配給孩子 i ,這個孩子會得到滿足。你的目標是儘可能滿足越多數量的孩子,並輸出這個最大數值。

注意:
你可以假設胃口值爲正。
一個小朋友最多隻能擁有一塊餅乾。

示例 1:

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

輸出: 1

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

示例 2:

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

輸出: 2

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

貪心就是給一個孩子的餅乾應當儘量小又能滿足該孩子,這樣大餅乾就能拿來給滿足度比較大的孩子。因爲最小的孩子最容易得到滿足,所以先滿足最小的孩子。通過使用這種貪婪的方法,浪費的cookie的總和將是最小的。因此要對兩個數組進行排序,然後進行比較即可。

//[1,2], [1,2,3]
class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int gi = 0, si = 0;
        while (si < s.length && gi < g.length) {
            if (g[gi] <= s[si]) {
                gi++;
            }
            si++;
            
        }
        return gi;
    }
}

435. Non-overlapping Intervals

題目描述:

給定一個區間的集合,找到需要移除區間的最小數量,使剩餘區間互不重疊。

注意:

可以認爲區間的終點總是大於它的起點。
區間 [1,2] 和 [2,3] 的邊界相互“接觸”,但沒有相互重疊。

示例 1:
輸入: [ [1,2], [2,3], [3,4], [1,3] ]
輸出: 1
解釋: 移除 [1,3] 後,剩下的區間沒有重疊。


示例 2:
輸入: [ [1,2], [1,2], [1,2] ]
輸出: 2
解釋: 你需要移除兩個 [1,2] 來使剩下的區間沒有重疊。


示例 3:
輸入: [ [1,2], [2,3] ]
輸出: 0
解釋: 你不需要移除任何區間,因爲它們已經是無重疊的了。
解題思路:

經典的貪婪問題:間隔調度,下面那一題LeetCode452也是同樣思路

先計算最多能組成的不重疊區間個數,然後用區間總個數減去不重疊區間的個數。

在每次選擇中,選擇的區間結尾越小,留給後面的區間的空間越大,那麼後面能夠選擇的區間個數也就越大。

按區間的結尾進行升序排序,每次選擇結尾最小,並且和前一個區間不重疊的區間。

在對數組進行排序的時候也可以使用 lambda 表示式來創建 Comparator ,不過算法運行時間會比較長點。

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        if (intervals.length == 0) {
            return 0;
        }
        Arrays.sort(intervals,new Comparator<int [] >(){
            public int compare(int [] a1,int [] a2) {
                return a1[1] - a2[1];   //升序排列
            }
        });
        int count = 1;	//最多能組成的不重疊區間個數
        int end = intervals[0][1];
        for (int i = 0; i < intervals.length; i++) {
            if (intervals[i][0] < end) {
                continue;
            }
            end = intervals[i][1];
            count++;
        }
        return intervals.length - count;
    }
}

452. Minimum Number of Arrows to Burst Balloon

題目描述:

在二維空間中有許多球形的氣球。對於每個氣球,提供的輸入是水平方向上,氣球直徑的開始和結束座標。由於它是水平的,所以y座標並不重要,因此只要知道開始和結束的x座標就足夠了。開始座標總是小於結束座標。平面內最多存在104個氣球。

一支弓箭可以沿着x軸從不同點完全垂直地射出。在座標x處射出一支箭,若有一個氣球的直徑的開始和結束座標爲 xstart,xend, 且滿足 xstart ≤ x ≤ xend,則該氣球會被引爆。可以射出的弓箭的數量沒有限制。 弓箭一旦被射出之後,可以無限地前進。我們想找到使得所有氣球全部被引爆,所需的弓箭的最小數量。

輸入:
[[10,16], [2,8], [1,6], [7,12]]

輸出:
2

解釋:
對於該樣例,我們可以在x = 6(射爆[2,8],[1,6]兩個氣球)和 x = 11(射爆另外兩個氣球)。
解題思路:

本題跟上一題幾乎一樣的實現方法,說白了就是計算不重疊的區間個數,並且按結束座標對數組進行排序,然後遍歷判斷
不過這裏注意[1, 2] 和 [2, 3] 在本題中算是重疊區間。在這裏我對數組進行排序時用lambda表達式,相對實現Comparator接口較慢。
在這裏插入圖片描述
代碼如下:

class Solution {
    public int findMinArrowShots(int[][] points) {
        if (points.length == 0) {
            return 0;
        }
        Arrays.sort(points,(i1,i2) -> (i1[1] - i2[1])); //使用Lambda表達式排序,按
        int count = 1;
        int end = points[0][1];
        for (int i = 0; i < points.length; i++) {
            if (points[i][0] <= end) {      //注意這裏是<=
                continue;		//屬於重複區間,跳過,不用增加count次數
            }
            end = points[i][1];
            count++;
        }
        return count;
    }
}

605. Can Place Flowers

題目描述:

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

給定一個花壇(表示爲一個數組包含0和1,其中0表示沒種植花,1表示種植了花),和一個數 n 。能否在不打破種植規則的情況下種入 n 朵花?能則返回True,不能則返回False。

解題思路:

貪婪地在從左到右遍歷,在遇到的每個空地上放一朵花。
獲取上一個和下一個花壇,如果i位於首位或者末端,則前一個或者下一個被認爲是0。

class Solution {
    public boolean canPlaceFlowers(int[] flowerbed, int n) {
        int length = flowerbed.length;
        int count = 0;
        for(int i = 0; i < length && count < n; i++) {
            if (flowerbed[i] == 0) {
                int pre = (i == 0)? 0 : flowerbed[i-1];	//上一個花壇
                int next = (i == length - 1)? 0 : flowerbed[i+1];	//下一個花壇
                if(pre == 0 && next == 0) {
                    count++;
                    flowerbed[i] = 1;
                }
            }
        }
        return count == n;
    }
}

121. Best Time to Buy and Sell Stock

題目描述:

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

如果你最多隻允許完成一筆交易(即買入和賣出一支股票),設計一個算法來計算你所能獲取的最大利潤。

注意你不能在買入股票前賣出股票。

示例 1:
輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
注意利潤不能是 7-1 = 6, 因爲賣出價格需要大於買入價格。
     
示例 2:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。
解題思路:

從頭遍歷數組,比較兩個數之中誰比較大,這裏注意賣出價格要大於買入價格,如果第一個值比第二個值大,由於貪心策略,要求最大收益,因此把最小价格賦值給第二個,如果第一個值比第二個小,則將當前價格 即 售出價格減去最小价格,則爲最大利益,遍歷完即可。

class Solution {
    public int maxProfit(int[] prices) {
        if (prices.length <= 1) {
            return 0;
        }
        int temp = prices[0];	//最小价格
        int max = 0;	//最大收益
        for (int i = 1; i < prices.length; i++){
            if (temp > prices[i]) {
                temp = prices[i];
            } else {
                max = Math.max(max,prices[i] - temp);
            }
        }
        return max;
    }
}

665. Non-decreasing Array

題目描述:

給定一個長度爲 n 的整數數組,你的任務是判斷在最多改變 1 個元素的情況下,該數組能否變成一個非遞減數列。

我們是這樣定義一個非遞減數列的: 對於數組中所有的 i (1 <= i < n),滿足 array[i] <= array[i + 1]。

示例 1:
輸入: [4,2,3]
輸出: True
解釋: 你可以通過把第一個4變成1來使得它成爲一個非遞減數列。

示例 2:

輸入: [4,2,1]
輸出: False
解釋: 你不能在只改變一個元素的情況下將其變爲非遞減數列。
說明:  n 的範圍爲 [1, 10,000]。
解題思路:

如果我們遇到一個失敗的情況,即不是非遞減數列,我們需要進行修正。可以通過以下兩種方式之一進行更正:

  1. 使前一個數字小於或等於當前數字
  2. 使當前數字等於先前的數字
    當找到nums[i-1] > nums[i],採用方式一,通過改變nums[i-1]的值,這樣不會影響了後續操作。還有,如果nums[i-2] > nums[i],採用方式二,改變nums[i]的值。
class Solution {
    public boolean checkPossibility(int[] nums) {
        int length = nums.length;
        int count = 0;
        for (int i = 1; i < length && count < 2; i++) {
            if (nums[i] >= nums[i - 1]) {
                continue;
            }
            count++;
            if (i - 2 >= 0 && nums[i - 2] > nums[i]) {
                nums[i] = nums[i - 1];	 //使當前數字等於先前的數字
            } else {
                nums[i - 1] = nums[i];	//使前一個數字小於或等於當前數字
            }
        }
        return count <= 1;
    }
}

關於LeetCode中貪心的題目就介紹這麼多啦,以後有空再回來重新複習下相似題目,繼續下一個tag的刷題,keep moving!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章