本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
There is a one-dimensional garden on the x-axis. The garden starts at the point 0 and ends at the point n. (i.e The length of the garden is n).
There are n + 1 taps located at points [0, 1, ..., n] in the garden.
Given an integer n and an integer array ranges of length n + 1 where ranges[i] (0-indexed) means the i-th tap can water the area [i - ranges[i], i + ranges[i]] if it was open.
Return the minimum number of taps that should be open to water the whole garden, If the garden cannot be watered return -1.
Example 1:
 
Input: n = 5, ranges = [3,4,1,1,0,0]
Output: 1
Explanation: The tap at point 0 can cover the interval [-3,3]
The tap at point 1 can cover the interval [-3,5]
The tap at point 2 can cover the interval [1,3]
The tap at point 3 can cover the interval [2,4]
The tap at point 4 can cover the interval [4,4]
The tap at point 5 can cover the interval [5,5]
Opening Only the second tap will water the whole garden [0,5]
 
Example 2:
Input: n = 3, ranges = [0,0,0,0]
Output: -1
Explanation: Even if you activate all the four taps you cannot water the whole garden.
 
Constraints:
1 <= n <= 104ranges.length == n + 10 <= ranges[i] <= 100
题意:x轴上一座一维花园 [0, n],长度为 n 。花园中共有 n + 1 个水龙头,分别位于 [0, 1, ..., n] ,整数数组 ranges 中的 ranges[i] 表示 i 处水龙头可以灌溉 [i -  ranges[i], i + ranges[i]] 区域。返回可以灌溉整个花园的最少水龙头数目,如果无法完全灌溉,则返回-1。
解法 贪心+桶排(最优)
首先解释示例2为什么要输出-1:因为「整个花园」包含不是整点的位置,即小数位置也要被灌溉到,但输入只能灌溉 0 , 1 , 2 , 3 0,1,2,3 0,1,2,3 这 4 4 4 个整点。
一般来说,这种覆盖区间类型的题,可以先进行常规排序再求解,但时间复杂度会到 O ( n log  n ) O(n\log n) O(nlogn) 。直观来说,第一次选择的区间,自然会想让它的左边界不大于0的同时,右边界尽可能地大,然后第二次选择区间,在其左边界不大于第一次选择的右边界的同时,右边界尽可能地大,依次类推,直至右边界到达n。如果用这种方法,我们就可以按照每一个区间的左边界,对其进行正序排序(不用排序二级排序右边界)。
借用别人的图:
 
由于这里区间的左边界是整数,可以使用桶排,设置一个 rightMost 数组,对当前的 i 及其半径 ranges[i] :
- 当 
i > ranges[i]时,对该水龙头能覆盖的最左边界i - ranges[i]而言,能覆盖的最右边界一定是i + ranges[i],即rightMost[i - ranges[i]] = i + ranges[i]。假如j < i,且j - ranges[j]能覆盖的最左边界也是i - ranges[i],则ranges[j]一定小于ranges[i]、且j + ranges[j] < i + ranges[i]。 - 当 
i <= ranges[i]时,更新rightMost[0]的值为max(rightMost[0], i + ranges[i],即灌溉最左边界小于等于0的水龙头中,灌溉最右边界不一定是i + ranges[i],取最大值。 
class Solution {
public:
    int minTaps(int n, vector<int>& ranges) {
        vector<int> rightMost(n + 1);
        for (int i = 0; i <= n; ++i) {
            int r = ranges[i]; // r是半径
            if (i > r) rightMost[i - r] = i + r; // 对于i-r来说,i+r必然是它目前的最大值
            else rightMost[0] = max(rightMost[0], i + r);
        }
        int ans = 0;
        int curRight = 0, nextRight = 0; // 已建造的桥的右端点,下一座桥的右端点的最大值
        for (int i = 0; i < n; ++i) { // 注意这里没有遍历到n,因为它已经是终点了
            nextRight = max(nextRight, rightMost[i]);
            if (i == curRight) { // 到达已被覆盖的灌溉处的右端点 // 到达已建造的桥的右端点 
                if (i == nextRight) return -1; // 无论怎么开水龙头,都无法从i灌溉到i+1 // 无论怎么造桥,都无法从i到i+1
                curRight = nextRight; // 开能到最右处的水龙头 // 造一座桥
                ++ans;
            }
        }
        return ans;
    }
}; 
                


















