本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
你的国家有无数个湖泊,所有湖泊一开始都是空的。当第 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]。
可以看出,这个方案下不会有洪水发生。同时, [-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
解法 贪心+二分+有序集合
我们要思考如何在洪水即将发生时,有选择地对湖泊进行抽干操作。
为了做到这一点,我们使用有序集合 st \textit{st} st 来存储了那些在某些日期没有下雨的日子。这些晴天日子可以被用来在湖泊即将发生洪水时,有选择地抽干湖泊,从而阻止洪水的发生。
有序集合 st \textit{st} st 的排序方式是按照晴天日子的顺序排列的,这就确保了我们总是在最早的晴天日子中进行抽干操作,以最大程度地避免洪水的发生。对于最后剩余的晴天,我们可以将它们用于抽干任意一个湖泊,为了方便,我们令其为 1 1 1 。
现在我们初始化一个大小和 r a i n s rains rains 一样的答案数组 a n s ans ans ,并初始化为 1 1 1 ,然后从左到右来遍历数组 r a i n s rains rains :
- 若 rains [ i ] = 0 \textit{rains}[i] = 0 rains[i]=0 ,则将 i i i 加入有序集合 st \textit{st} st 。
- 若
rains
[
i
]
>
0
\textit{rains}[i] > 0
rains[i]>0 ,表示第
rains
[
i
]
\textit{rains}[i]
rains[i] 湖泊将下雨,令
ans
[
i
]
=
−
1
\textit{ans}[i] = -1
ans[i]=−1 表示这一天的湖泊不可抽干:
- 若第 rains [ i ] \textit{rains}[i] rains[i] 是第一次下雨,则此时不会发生洪水。
- 否则我们需要在有序集合 st \textit{st} st 中找到大于等于该湖泊上一次下雨天数的最小索引 idx \textit{idx} idx(可以用二分查找实现),如果 idx \textit{idx} idx 不存在(即没有晴天可以用于抽干),此时不能避免洪水的发生,按照题目要求返回一个空数组。
- 否则我们令 a n s [ i d x ] = r a i n s [ i ] ans[idx]=rains[i] ans[idx]=rains[i] ,并在 st \textit{st} st 中删除 idx \textit{idx} idx ,表示我们会在第 idx \textit{idx} idx 天抽干 r a i n s [ i ] rains[i] rains[i] 湖泊的水来避免第 i i i 天洪水的发生。
class Solution {
public:
vector<int> avoidFlood(vector<int>& rains) {
int n = rains.size();
vector<int> ans(n, 1);
set<int> st;
unordered_map<int, int> rec;
for (int i = 0; i < n; ++i) {
if (rains[i] == 0) st.insert(i);
else {
ans[i] = -1;
if (rec.count(rains[i])) {
// 找到第一个>rec[rains[i]](即该湖泊上次下雨的时间)的晴天下标
auto it = st.lower_bound(rec[rains[i]]);
if (it == st.end()) return {};
ans[*it] = rains[i];
st.erase(it);
}
rec[rains[i]] = i;
}
}
return ans;
}
};
复杂度分析:
- 时间复杂度: O ( n × log n ) O(n \times \log n) O(n×logn) ,其中 n n n 为数组 rains \textit{rains} rains 的长度。每次有序集合的插入和查询的时间复杂度为 O ( log n ) O(\log n) O(logn) ,最坏情况下会进行 n n n 次有序集合的插入操作,所以总的时间复杂度为 O ( n × log n ) O(n \times \log n) O(n×logn) 。
- 空间复杂度: O ( n ) O(n) O(n) ,其中 n n n 为数组 rains \textit{rains} rains 的长度。主要为哈希表和有序集合的空间开销。