
你的国家有无数个湖泊,所有湖泊一开始都是空的。当第 n 个湖泊下雨前是空的,那么它就会装满水。如果第 n 个湖泊下雨前是 满的 ,这个湖泊会发生 洪水 。你的目标是避免任意一个湖泊发生洪水。
给你一个整数数组 rains ,其中:
rains[i] > 0 表示第 i 天时,第 rains[i] 个湖泊会下雨。rains[i] == 0 表示第 i 天没有湖泊会下雨,你可以选择 一个 湖泊并 抽干 这个湖泊的水。请返回一个数组 ans ,满足:
ans.length == rains.lengthrains[i] > 0 ,那么 ans[i] == -1 。rains[i] == 0 ,ans[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^50 <= rains[i] <= 10^9class Solution {
public int[] avoidFlood(int[] rains) {
}
}
对于这个题目, 如果洪水泛滥, 那么在数据上会有怎么的规律呢?
洪水泛滥的数据规律:
对于第i个湖, 下雨 → 下雨 → 泛滥

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

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

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

接下来, 我们看一下具体的解题过程.
首先初始化各个空间, 其中 result 是结果数组, zeroIndexs 是存储的抽干湖的机会所在天数(所在下标), cache 表示湖的水量, key是湖的下标, value表示湖的水量.
int[] result = new int[rains.length];
List zeroIndexs = new ArrayList<>();
Map 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 zeroIndexs = new ArrayList<>();
// 创建一个字典, key是湖的下标, value是湖的水量==1的天数
Map 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;
}
}
复杂度分析:
n 是 天气情况数组的长度. m 是 zeroIndexs 数组的长度.zeroIndexs 、 cache 的空间复杂度都与元素个数长度相关.结果如下所示.
