如何避免洪水泛滥

153 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情

一、题目

你的国家有无数个湖泊,所有湖泊一开始都是空的。当第 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] == 0 ,ans[i] 是你第 i 天选择抽干的湖泊。

如果有多种可行解,请返回它们中的 任意一个 。如果没办法阻止洪水,请返回一个 空的数组 。

leetcode

二、分析

这题不看解释,读不懂题,翻译的啥玩意。。。其次还有坑的地方,没湖可抽,为啥返回1号湖,题目也没说清楚呀!!!

下雨天不干活,干活的时候不下雨。

下雨天没法干活,只有不下雨的时候选一个(选的时候有讲究)湖泊(i)把水抽干才能避免下次下雨导致湖泊(i)发生洪水,因为原来湖泊(i)有水不抽干,再次下雨就导致发生洪水了

准备一个表:存放有雨的湖泊号(没抽干的湖泊),当不下雨的时候,看右边哪个湖离的近,就抽那个湖

当来到不下雨的时候,怎么快速查找到比较近的湖泊呢?不通过遍历的方式

{1, 2, 0, 2, 3, 0, 1, 0, 3}
 0  1  2  3  4  5  6  7  8

准备一张表(每个湖泊在哪天下雨):key:湖泊号, value:天(数组下标)列表
1:{0,6}, 2:{1,3}, 3:{4,8}

set(存放有雨水的湖泊,待抽水)

有序表或小根堆(干活表), 根据天(数组下标)从小到大排序

三、实现

// rains[i] = j 第i天轮到j号湖泊下雨
// 规定,下雨日,不干活 : -1
// 不下雨日,如果没有湖泊可抽 : 1
public static int[] avoidFlood(int[] rains) {
    int n = rains.length;
    int[] ans = new int[n];
    int[] invalid = new int[0];
    // key : 某个湖
    // value : 这个湖在哪些位置降雨
    // 4 : {3,7,19,21}
    // 1 : { 13 }
    // 2 : {4, 56}
    HashMap<Integer, LinkedList<Integer>> map = new HashMap<>();
    for (int i = 0; i < n; i++) {
        if (rains[i] != 0) { // 第i天要下雨,rains[i]
            // 3天 9号
            // 9号 { 3 }
            // 9号 {1, 3}
            if (!map.containsKey(rains[i])) {
                map.put(rains[i], new LinkedList<>());
            }
            map.get(rains[i]).addLast(i);
        }
    }
    // 没抽干的湖泊表
    // 某个湖如果满了,加入到set里
    // 某个湖被抽干了,从set中移除
    HashSet<Integer> set = new HashSet<>();
    // 这个堆的堆顶表示最先处理的湖是哪个
    PriorityQueue<Work> heap = new PriorityQueue<>();
    for (int i = 0; i < n; i++) { // 0 1 2 3 ...
        if (rains[i] != 0) { // 当前湖有水
            if (set.contains(rains[i])) {
                return invalid;
            }
            // 放入到没抽干的表里
            set.add(rains[i]);
            map.get(rains[i]).pollFirst();
            if (!map.get(rains[i]).isEmpty()) {
                heap.add(new Work(rains[i], map.get(rains[i]).peekFirst()));
            }
            // 题目规定
            ans[i] = -1;
        } else { // 今天干活!
            if (heap.isEmpty()) { // 不下雨日,如果没有湖泊可抽 : 1
                ans[i] = 1;
            } else {
                Work cur = heap.poll();
                set.remove(cur.lake);
                ans[i] = cur.lake;
            }
        }
    }
    return ans;
}

public static class Work implements Comparable<Work> {
    public int lake;
    public int nextRain;

    public Work(int l, int p) {
        lake = l;
        nextRain = p;
    }

    @Override
    public int compareTo(Work o) {
        return nextRain - o.nextRain;
    }

}

四、总结

整体时间复杂度为O(N),遍历一次搞定

堆的调整代价为O(logN)

如果通过遍历查找,则整体时间复杂度为O(N²)