用最少數量的箭引爆氣球
在二維空間中有許多球形的氣球。對於每個氣球,提供的輸入是水平方向上,氣球直徑的開始和結束座標。由於它是水平的,所以縱座標並不重要,因此只要知道開始和結束的橫座標就足夠了。開始座標總是小於結束座標。
一支弓箭可以沿著 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;
}
};
無重疊區間
給定一個區間的集合,找到需要移除區間的最小數量,使剩餘區間互不重疊。
注意: 可以認為區間的終點總是大於它的起點。 區間 [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