Medium
题目描述
你的国家有 10^9 个湖泊。最初,所有湖泊都是空的,但当第 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 天你选择抽干的湖泊
如果有多个有效答案,返回其中任何一个。如果无法避免洪水,返回空数组。
注意,如果你选择抽干一个满的湖泊,它会变空;但如果你选择抽干一个空的湖泊,什么都不会改变。
示例 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]。
示例 3:
输入: rains = [1,2,0,1,2]
输出: []
解释: 第二天后,满湖泊为 [1,2]。我们必须在第三天抽干一个湖泊。
之后,湖泊 [1,2] 会下雨。无论第三天选择抽干哪个湖泊,另一个都会发生洪水。
约束条件:
- 1 <= rains.length <= 10^5
- 0 <= rains[i] <= 10^9
解题思路
这是一个贪心策略问题,核心思想是提前规划抽干湖泊的时机。
思路分析:
数据结构准备:
- 使用哈希表
lastRain记录每个湖泊最近一次下雨的天数 - 使用集合
dryDays保存可用的抽干天数(rains[i] == 0 的天) - 使用结果数组
ans记录每天的操作
- 使用哈希表
核心策略:
- 当某一天下雨时(rains[i] > 0),检查该湖泊是否已经满了
- 如果湖泊已满,需要在之前的某个抽干日将其抽干
- 关键是选择最早的、且在该湖泊上次下雨之后的抽干日
贪心选择:
- 对于每个需要抽干的湖泊,选择最早可用的抽干日
- 这样可以为后续可能的冲突保留更多选择
实现细节:
- 遍历数组,遇到下雨日检查冲突并分配抽干日
- 遇到抽干日先加入可用集合,后续分配时再移除
- 如果无法找到合适的抽干日,返回空数组
算法流程:
- 初始化数据结构
- 遍历每一天的天气情况
- 处理下雨日:检查冲突并分配抽干任务
- 处理抽干日:加入可用天数集合
- 为未分配的抽干日随意分配湖泊(选1)
代码实现
class Solution {
public:
vector<int> avoidFlood(vector<int>& rains) {
int n = rains.size();
vector<int> ans(n);
unordered_map<int, int> lastRain; // 湖泊 -> 最近下雨的天数
set<int> dryDays; // 可用的抽干天数
for (int i = 0; i < n; i++) {
if (rains[i] > 0) {
// 下雨天
ans[i] = -1;
if (lastRain.count(rains[i])) {
// 湖泊已满,需要找到一个抽干日
int lastDay = lastRain[rains[i]];
auto it = dryDays.upper_bound(lastDay);
if (it == dryDays.end()) {
return {}; // 无法避免洪水
}
ans[*it] = rains[i]; // 在该天抽干这个湖泊
dryDays.erase(it);
}
lastRain[rains[i]] = i;
} else {
// 抽干天
dryDays.insert(i);
}
}
// 为剩余的抽干天随意分配湖泊
for (int day : dryDays) {
ans[day] = 1;
}
return ans;
}
};
class Solution:
def avoidFlood(self, rains: List[int]) -> List[int]:
n = len(rains)
ans = [0] * n
last_rain = {} # 湖泊 -> 最近下雨的天数
dry_days = set() # 可用的抽干天数
for i in range(n):
if rains[i] > 0:
# 下雨天
ans[i] = -1
if rains[i] in last_rain:
# 湖泊已满,需要找到一个抽干日
last_day = last_rain[rains[i]]
# 找到第一个大于last_day的抽干日
available = [day for day in dry_days if day > last_day]
if not available:
return [] # 无法避免洪水
dry_day = min(available)
ans[dry_day] = rains[i] # 在该天抽干这个湖泊
dry_days.remove(dry_day)
last_rain[rains[i]] = i
else:
# 抽干天
dry_days.add(i)
# 为剩余的抽干天随意分配湖泊
for day in dry_days:
ans[day] = 1
return ans
public class Solution {
public int[] AvoidFlood(int[] rains) {
int n = rains.Length;
int[] ans = new int[n];
Dictionary<int, int> lastRain = new Dictionary<int, int>(); // 湖泊 -> 最近下雨的天数
SortedSet<int> dryDays = new SortedSet<int>(); // 可用的抽干天数
for (int i = 0; i < n; i++) {
if (rains[i] > 0) {
// 下雨天
ans[i] = -1;
if (lastRain.ContainsKey(rains[i])) {
// 湖泊已满,需要找到一个抽干日
int lastDay = lastRain[rains[i]];
var availableDays = dryDays.GetViewBetween(lastDay + 1, int.MaxValue);
if (availableDays.Count == 0) {
return new int[0]; // 无法避免洪水
}
int dryDay = availableDays.Min;
ans[dryDay] = rains[i]; // 在该天抽干这个湖泊
dryDays.Remove(dryDay);
}
lastRain[rains[i]] = i;
} else {
// 抽干天
dryDays.Add(i);
}
}
// 为剩余的抽干天随意分配湖泊
foreach (int day in dryDays) {
ans[day] = 1;
}
return ans;
}
}
var avoidFlood = function(rains) {
const result = new Array(rains.length);
const fullLakes = new Set();
const dryDays = [];
for (let i = 0; i < rains.length; i++) {
if (rains[i] === 0) {
dryDays.push(i);
result[i] = 1;
} else {
if (fullLakes.has(rains[i])) {
let dryDayIndex = -1;
for (let j = 0; j < dryDays.length; j++) {
if (dryDays[j] > -1) {
dryDayIndex = j;
break;
}
}
if (dryDayIndex === -1) {
return [];
}
const dayToDry = dryDays[dryDayIndex];
result[dayToDry] = rains[i];
dryDays[dryDayIndex] = -1;
fullLakes.delete(rains[i]);
}
fullLakes.add(rains[i]);
result[i] = -1;
}
}
return result;
};
复杂度分析
| 复杂度类型 | C++ | Python | C# | JavaScript |
|---|---|---|---|---|
| 时间复杂度 | O(n log n) | O(n²) | O(n log n) | O(n²) |
| 空间复杂度 | O(n) | O(n) | O(n) | O(n) |
说明:
- C++ 和 C# 使用了有序集合,查找操作为 O(log n)
- Python 和 JavaScript 版本中查找最小可用天数需要遍历集合,为 O(n)
- 总体时间复杂度主要由查找操作决定
- 空间复杂度主要用于存储湖泊状态和可用抽干天数