1488. 避免洪水泛滥

难度: 中等
来源: 每日一题 2023.10.13

你的国家有无数个湖泊,所有湖泊一开始都是空的。当第 n 个湖泊下雨前是空的,那么它就会装满水。如果第 n 个湖泊下雨前是 满的 ,这个湖泊会发生 洪水 。你的目标是避免任意一个湖泊发生洪水。

给你一个整数数组 rains ,其中:

  • rains[i] > 0 表示第 i 天时,第 rains[i] 个湖泊会下雨。
  • rains[i] == 0 表示第 i 天没有湖泊会下雨,你可以选择 一个 湖泊并 抽干 这个湖泊的水。

请返回一个数组 ans ,满足:

  • ans.length == rains.length
  • 如果 rains[i] > 0,那么 ans[i] == -1
  • 如果 rains[i] == 0ans[i] 是你第 i 天选择抽干的湖泊。
  • 如果有多种可行解,请返回它们中的 任意一个 。如果没办法阻止洪水,请返回一个 空的数组

请注意,如果你选择抽干一个装满水的湖泊,它会变成一个空的湖泊。但如果你选择抽干一个空的湖泊,那么将无事发生。

示例 1:

输入:rains = [1,2,3,4]
输出:[-1,-1,-1,-1]
解释:第一天后,装满水的湖泊包括 [1]
第二天后,装满水的湖泊包括 [1,2]
第三天后,装满水的湖泊包括 [1,2,3]
第四天后,装满水的湖泊包括 [1,2,3,4]
没有哪一天你可以抽干任何湖泊的水,也没有湖泊会发生洪水。

示例 2:

输入:rains = [1,2,0,0,2,1]
输出:[-1,-1,2,1,-1,-1]
解释:第一天后,装满水的湖泊包括 [1]
第二天后,装满水的湖泊包括 [1,2]
第三天后,我们抽干湖泊 2 。所以剩下装满水的湖泊包括 [1]
第四天后,我们抽干湖泊 1 。所以暂时没有装满水的湖泊了。
第五天后,装满水的湖泊包括 [2]。
第六天后,装满水的湖泊包括 [1,2]。
可以看出,这个方案下不会有洪水发生。同时, [-1,-1,1,2,-1,-1] 也是另一个可行的没有洪水的方案。

示例 3:

输入:rains = [1,2,0,1,2]
输出:[]
解释:第二天后,装满水的湖泊包括 [1,2]。我们可以在第三天抽干一个湖泊的水。
但第三天后,湖泊 1 和 2 都会再次下雨,所以不管我们第三天抽干哪个湖泊的水,另一个湖泊都会发生洪水。

提示:

  • 1 <= rains.length <= 10^5
  • 0 <= rains[i] <= 10^9
class Solution {
    public int[] avoidFlood(int[] rains) {

    }
}

分析与题解


  • 贪心算法

    对于这个题目, 如果洪水泛滥, 那么在数据上会有怎么的规律呢?

    洪水泛滥的数据规律:

    对于第i个湖, 下雨 → 下雨 → 泛滥

    对于第i个湖, 下雨 → 抽干 → 下雨 → 无事

    也就说 对于第i个湖, 两次下雨之间一定要有一次能抽水的机会, 才能避免泛滥

    所以对于能抽干湖的机会, 我们要保存下来它的所在天数(所在下标), 当后面某 i 天, 当某一个湖要泛滥时, 我们尝试往前找能抽干湖的机会. 找到了就万事大吉. 找不到就直接导致洪水泛滥, GG.

    leetcode1488_3-2023-10-13-15-01-18

    另外有如图的情况, 红色的湖桔色的湖 都要泛滥了, 单独来看每一个湖都可以在两次机会中任选一个, 但是两者合在一起的时候, 红色的湖 一定选择前面的抽干机会, 桔色的湖才能不会泛滥...

    接下来, 我们看一下具体的解题过程.

    首先初始化各个空间, 其中 result 是结果数组, zeroIndexs 是存储的抽干湖的机会所在天数(所在下标), cache 表示湖的水量, key是湖的下标, value表示湖的水量.

    int[] result = new int[rains.length];
    List<Integer> zeroIndexs = new ArrayList<>();
    Map<Integer, Integer> cache = new HashMap<>();
    

    然后我们遍历 rains 数组.

    在遍历过程中, 首先有这样的几种情况需要进行不同的逻辑分支.

    • 当前有抽干湖的机会, 添加到 zeroIndexs 中. result[i] = 1; 则是初始化一个值,后期没人使用这次机会那就让这次机会抽第 1 个湖.

      // 添加豁免权
      if(rains[i] == 0) {
          zeroIndexs.add(i);
          result[i] = 1;
          continue;
      }
      
    • 如果是正常下雨天数并且当前湖里没有水, 那就直接下雨填满湖就行.

      // 正常流程
      Integer value = cache.getOrDefault(rains[i], -1);
      if(value == -1) {
          // 没有水
          cache.put(rains[i], i);
          result[i] = -1;
      }       
      
    • 如果是下雨天但是当前湖里有水, 我们就要从 zeroIndexs 找到合适的机会抽水, 如果找不到,那就洪水泛滥了...

      从前往后查找, 并且 下雨天下标 < 抽水天下标 < 下雨天下标

      boolean isSuccess = false;
      for(int j = 0; j < zeroIndexs.size(); j++) {
          Integer index = zeroIndexs.get(j);
          if(index > value && index < i) {
              result[index] = rains[i];
              zeroIndexs.remove(j);
              cache.put(rains[i], i);
              isSuccess = true;
              break;
          }
      }
      // 对于某一个湖 一定是 先下雨 抽水 再下雨这个流程, 而不能是 抽水 下雨 再下雨.
      // 这个逻辑通过判断先前的下雨的下标和最后一次豁免权的下标来的.
      if(!isSuccess) {
          // 没有豁免权在两次下雨中间,泛滥
          return new int[0];
      }
      result[i] = -1;
      

    接下来, 我们一起看一下整体的解题过程.

    class Solution {
        public int[] avoidFlood(int[] rains) {
            int[] result = new int[rains.length];
            // 事件一共有三种
            // 下雨, 抽水, 泛滥
    
            // 0代表着豁免权, 也就是当某一个湖要泛滥时(>=2), 我们可以用在前面的某一个位置的0抽水,防止泛滥
            // 如果zeroIndexs为空,则代表着没有能力阻止泛滥
            List<Integer> zeroIndexs = new ArrayList<>();
            // 创建一个字典, key是湖的下标, value是湖的水量==1的天数
            Map<Integer, Integer> cache = new HashMap<>();
            for(int i = 0; i < rains.length; i++) {
                // 添加豁免权
                if(rains[i] == 0) {
                    zeroIndexs.add(i);
                    result[i] = 1;
                    continue;
                }
                // 正常流程
                Integer value = cache.getOrDefault(rains[i], -1);
                if(value == -1) {
                    // 没有水
                    cache.put(rains[i], i);
                    result[i] = -1;
                } else {
                    boolean isSuccess = false;
                    for(int j = 0; j < zeroIndexs.size(); j++) {
                        Integer index = zeroIndexs.get(j);
                        if(index > value && index < i) {
                            result[index] = rains[i];
                            zeroIndexs.remove(j);
                            cache.put(rains[i], i);
                            isSuccess = true;
                            break;
                        }
                    }
                    // 对于某一个湖 一定是 先下雨 抽水 再下雨这个流程, 而不能是 抽水 下雨 再下雨.
                    // 这个逻辑通过判断先前的下雨的下标和最后一次豁免权的下标来的.
                    if(!isSuccess) {
                        // 没有豁免权在两次下雨中间,泛滥
                        return new int[0];
                    }
                    result[i] = -1;
                }
            }
    
            return result;
        }
    }
    

    复杂度分析:

    • 时间复杂度: O(nm) , n 是 天气情况数组的长度. mzeroIndexs 数组的长度.
    • 空间复杂度: O(2n), zeroIndexscache 的空间复杂度都与元素个数长度相关.

    结果如下所示.


IT界无底坑洞栋主 欢迎加Q骚扰:676758285