【LeetCode貪心#09】用最少數量的箭引爆氣球,(涉及區間重疊情況判斷)

dayceng發表於2023-03-19

用最少數量的箭引爆氣球

力扣題目連結(opens new window)

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

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

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

示例 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

提示:

  • 0 <= points.length <= 10^4
  • points[i].length == 2
  • -2^31 <= xstart < xend <= 2^31 - 1

思路

題意理解

題幹描述套了一個“打氣球”作為背景,搞得有點抽象,其實就是給一個二維陣列,尋找裡面子陣列的重合區間的一個問題

題意圖解如下:

  • 輸入:points = [[1,2],[3,6],[4,8],[7,12],[10,16]]
  • 輸出:3

如圖所示,[1,2]和[3,6]兩個區間所代表的的“氣球”沒有重合,因此不能同時打爆

而[3,6]和[4,8]位置的兩個“氣球”是有重合的,因此可以使用一支箭同時打爆

同理[7,12]和[10,16]位置也可以消耗一支箭打爆兩個氣球,因此要打爆所有氣球,最少使用的箭數是3

你可能會想:那為什麼不能同時打爆[4,8]和[7,12]位置的氣球呢?

如果是上面這種打法,那不重疊的[3,6]和[10,16]區間又分別需要消耗兩支箭,導致整體使用箭數不是最少

因此同時打爆[4,8]和[7,12]位置的氣球不是最優解

由上述討論可以得出貪心思路:

區域性最優:讓重疊區間儘可能多的在一塊,好一次打完

全域性最優:使用最少的箭打完所有氣球

本題難點有兩個:

1、如何模擬射氣球的過程

2、如何記錄重疊陣列

過程模擬

模擬射氣球時,不需要真的去刪除被射中的氣球陣列,只需計數即可

還是以上面的圖為例

先將所有氣球按照左邊界排序

情況1:不重疊,需要額外使用箭

以[1,2]和[3,6]為例,[3,6]的左邊界3大於[1,2]的右邊界2,因此這兩個區間無重疊,需要兩支箭分別射擊,即:

if(points[i][0] > points[i - 1][1]){//左邊界3大於右邊界2
    res++;//使用弓箭數增加	
}
情況2:重疊檢查並更新右邊界

以[3,6]和[4,8]為例,顯然這兩個位置的氣球時重疊的,那肯定就不滿足情況1的條件,因此我們可以接著情況1寫在else裡面

if(points[i][0] > points[i - 1][1]){//左邊界3大於右邊界2
    res++;//使用弓箭數增加	
}else{//(points[i][0] <= points[i - 1][1]的情況
    
}

為什麼有等於?因為題目說了挨著的氣球也可以一次打爆

當氣球有重合時,我們不需要使用新的弓箭。但是如果遍歷到下一個區間,它也與之前的區間有重合,我們怎麼去處理這種情況?

這個時候就需要在遇到重疊區間時,記錄當前最小的右邊界以提供給下一個區間進行重疊判斷

如圖所示,當下標i遍歷到[4,8]區間時,其與下標為i - 1的[3,6]區間有重合

此時不知道之後會不會還有區間與這兩個區間有重合,所以需要記錄下這兩個區間中最小的那個右邊界用於後續判斷

最小右邊界顯然是6,當下標i遍歷到[7,12]時,[7,12]的左邊界大於當前的最小右邊界6

所以[7,12]沒有與[3,6]、[4,8]有重疊部分,因此需要額外的弓箭來射擊(滿足情況1)

所以情況2的程式碼邏輯如下:

if(points[i][0] > points[i - 1][1]){//左邊界3大於右邊界2
    res++;//使用弓箭數增加	
}else{//(points[i][0] <= points[i - 1][1]的情況
    points[i][1] = min(points[i - 1][1], points[i][1]);//取兩個重合區間中最小的右邊界作為當前右邊界
}

程式碼

根據上述分析可以總結出以下步驟:

1、自定義比較規則cmp,按左邊界對陣列先進行排序

2、判斷陣列為0的情況

2、初始化弓箭數量變數(注意初始值為1,因為0的情況已經判斷過,所以之後的情況至少需要一支弓箭)

3、遍歷陣列,判斷兩種情況

  • 情況1,無重疊,更新res值記錄所需的新弓箭
  • 情況2,有重疊,更新最小右邊界,即(points[i] [0] <= points[i - 1] [1]
class Solution {
public:
    static bool cmp(const vector<int>& a, const vector<int>& b){
        return a[0] < b[0];
    }
    int findMinArrowShots(vector<vector<int>>& points) {
        if (points.size() == 0) return 0;//先判斷陣列為0的情況
        //對二維陣列進行排序
        sort(points.begin(), points.end(),cmp);
        int res = 1;//記錄弓箭使用數量,因為0的情況已經判斷過,所以之後的情況至少需要一支弓箭
        //遍歷陣列
        for(int i = 1; i < points.size(); ++i){//從1開始,不然比到最後會有負數
            //判斷左右邊界情況,即重疊情況
            if(points[i][0] > points[i - 1][1]){//情況1,無重疊
                res++;
            }else{//情況2,有重疊,更新最小右邊界//(points[i][0] <= points[i - 1][1]
                points[i][1] = min(points[i - 1][1], points[i][1]);
            }
        }
        return res;
    }
};

無重疊區間

力扣題目連結(opens new window)

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

注意: 可以認為區間的終點總是大於它的起點。 區間 [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
  • 解釋: 你不需要移除任何區間,因為它們已經是無重疊的了。

思路

做過射氣球那題之後,這題就很好理解了

整體思路無非也是排序(左右邊界均可,這裡還是和上題保持一致,用左邊界)後找出重疊區間,然後進行對應的處理

程式碼

和上題一樣,沒意思

步驟:

1、自定義排序

2、定義變數記錄需要刪除的區間的個數

3、遍歷區間,判斷重疊情況

  • 左邊界大於等於右邊界(區別於上一題,本題相鄰的情況也算作不重疊),無重疊,不進行任何操作
  • 左邊界小於右邊界,區間重疊,當前區間為待刪除區間,更新刪除區間的數量
class Solution {
public:
    static bool cmp(const vector<int>& a, const vector<int>& b){
        return a[0] < b[0];
    } 
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        //排序
        sort(intervals.begin(), intervals.end(), cmp);
        int removeNums = 0;//記錄移除區間數量,從1開始
        //遍歷
        for(int i = 1; i < intervals.size(); ++i){
            if(intervals[i][0] >= intervals[i - 1][1]){//不重疊,無操作

            }else{//區間重疊
                intervals[i][1] = min(intervals[i - 1][1], intervals[i][1]);//更新當前重疊區間中的最細小右邊界的
                removeNums++;//記錄需要移除的區間
            }
        }
        return removeNums;
    }
};

總結

TBD

相關文章