经典贪心:用最少数量的箭引爆气球

360 阅读2分钟

用最少数量的箭引爆气球

给出若干个区间,寻找最少个点的个数,可以命中所有区间。

示例:

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

输出:2

如果知道该题是一道贪心问题,贪心策略无非就是排序加遍历,至于是按起始位置排序还是按结束位置排序,稍微推算一下也容易得出,问题的关键在于如何发现这是个贪心问题。

我们不妨让第一个点就从零点开始。

i = 0  hit 0
i = 1  hit 1    [1,6]
i = 2  hit 2    [1,6] [2,8]
...
i = 6  hit 2    [1,6] [2,8]
i = 7  hit 2    [2,8] [7,12]    # [1,6]消失,确定1个点,命中[1,6] [2,8]
i = 8  hit 2    [2,8] [7,12]
i = 9  hit 1    [7,12]
i = 10 hit 2    [7,12] [10,16]
i = 11 hit 2    [7,12] [10,16]
i = 12 hit 2    [7,12] [10,16]
i = 13 hit 1    [10,16]         # [7,12]消失,确定1个点,命中[7,12] [10,16]
...
i = 16 hit 1    [10,16]

为什么一个区间从已命中集合中消失,就可以确定一个点?因为在该点之后的所有点都不会命中该区间了,同时保证了每确定的一个点都尽量让其命中更多的区间。故该问题可贪心解决。

我们将所有区间按结束位置排序。

0 1 2 3 4 5 6 7 8 9 10 11 12 16 14 15 16 17
  1 2 3 4 5 6
    2 3 4 5 6 7 8
              7 8 9 10 11 12
                    10 11 12 13 14 15 16

我们自左向右遍历,若想命中第一个区间的同时,命中尽可能多的区间,只能够在第一个区间末确定一个点。即6位置,可命中两个区间。剩余的区间同理。

这么看上去按照起始位置排序也没问题,不妨换一组样例试一试。

0 1 2 3 4 5 6 7 8 9 10 11 12 16 14 15 16 17
  1 2 3 4 5 6 7 8 9 10
      3 4 5 6
                  9 10 11 12 13 14
                       11 12 13 14 15 16
    
      3 4 5 6 
  1 2 3 4 5 6 7 8 9 10
                  9 10 11 12 13 14
                       11 12 13 14 15 16

上面是按照起始位置排序,下面是按照结束位置排序。可以看到,按结束位置排序我们只需要在遍历的过程中记录未命中区间集合中的第一个区间的右端点,即为最优解。

最终代码如下。

int findMinArrowShots(vector<vector<int>>& points) {
    if (0 == points.size()) return 0;
    sort(points.begin(), points.end(), [](auto& a, auto& b) {
        return a[1] < b[1];
    });
    int c = 1, e = points[0][1];
    for (int i = 0; i < points.size(); ++i) {
        if (e < points[i][0]) {
            ++c;
            e = points[i][1];
        }
    }
    return c;
}