1488. Avoid Flood in The City
Problem Description
In this problem, we are given a scenario involving lakes that can collect rainwater and a series of days with varying weather conditions. The task is to devise a strategy to prevent any lake from overflowing, given knowledge of rainfall on specific lakes on each day.
We are provided with an array, rains
, where each element corresponds to a day. If rains[i] > 0
, it means lake rains[i]
receives rain, and if rains[i] == 0
, it signifies a sunny day with no rain, allowing us the opportunity to dry one lake of our choosing.
To avoid flooding, if a lake is already full and it begins to rain on it again, we must have dried it on a previous sunny day; otherwise, a flood will occur.
Our objective is to return an array, ans
, of the same length as rains
that records our actions:
- We set
ans[i]
to-1
ifrains[i] > 0
to denote we can't perform any action other than observing the lake fill. - On sunny days, i.e., when
rains[i] == 0
,ans[i]
should be the index of the lake we choose to dry, if possible.
If there's no way to avoid a flood with the given sequence of rainy and sunny days, we must return an empty array. Otherwise, we may return any valid sequence of actions that prevents flooding.
Intuition
To solve this puzzle, we need to track when lakes get full and strategically use sunny days to prevent imminent floods. This requires recording the last day any lake received rain and planning for the next rain event on that lake. Since we must dry a lake before it overflows, we must act on a sunny day occurring after the last rainfall but before the next one on that same lake.
Here's how we can approach this problem:
-
Keep track of all sunny days in a sorted fashion, to efficiently find future opportunities to dry lakes. We utilize a sorted data structure, like a SortedList, or a priority queue for this purpose.
-
Use a hash table to remember the last rainy day for each lake. This will help us link future rainfalls to our records and identify necessary actions.
-
Initialize an answer array,
ans
, filled with-1
s, as our default action on rainy days is to do nothing.
As we traverse the rains
array:
- For rainy days (where
rains[i] > 0
), check if the current lake (rains[i]
) received rain in the past.- If it did, look for the next available sunny day after this past rainy day using binary search in the sorted sunny days list. Then, place the lake's index in the
ans
array on that sunny day to indicate we dry the lake to prevent a flood. If no suitable sunny day can be found, it means we cannot prevent a flood, and we should return an empty array. - Update the hash table with the current day for this lake.
- If it did, look for the next available sunny day after this past rainy day using binary search in the sorted sunny days list. Then, place the lake's index in the
- For sunny days (where
rains[i] == 0
):- Include this day in our sorted sunny days list for future potential use.
- Set
ans[i]
to1
, indicating that we could dry any lake if necessary, though the actual choice is arbitrary in the absence of constraints.
After the entire rains
array has been processed, return the ans
array as the solution. This answer prevents floods through tactical lake drying on sunny days that appropriately follow rainy days. The solution's correctness relies on efficiently pairing sunny days to rainy days just in time to avert floods, which is a characteristic of the Greedy approach combined with Binary Search for optimization.
Learn more about Greedy, Binary Search and Heap (Priority Queue) patterns.
Solution Approach
The implementation is a fine blend of greedy strategy and efficient searching through the use of a binary search algorithm. Here's a step-by-step walkthrough of the approach used in the solution:
-
Initialization: We begin by defining our answer array
ans
filled with-1
, a SortedListsunny
which will keep track of all sunny days, and a dictionaryrainy
which maps a lake (v
) to the last day it rained on that lake. -
Traversing
rains
Array: We iterate over each element in therains
array. Each elementrains[i]
corresponds to a day's forecast; hence we handle two cases:- If it's a rainy day (
rains[i] > 0
):- Check if the lake has already been filled before by searching for it in the
rainy
dictionary. - If it's been filled, we need to find a day in
sunny
to dry it before it rains again. We use thebisect_right
method, which effectively performs a binary search to find the index of the first sunny day after the last recorded rain on that lake. - If such a day doesn't exist in
sunny
(i.e.,idx == len(sunny)
), we have no way to dry the lake before the next rain, and thus, return an empty array to imply a flood is unavoidable. - Otherwise, allocate the lake number
v
to be dried on the found sunny day inans
, and then remove that day fromsunny
using thediscard
method since that day is now used up. - Update the
rainy
dictionary to record this latest rain day for the lake.
- Check if the lake has already been filled before by searching for it in the
- If it's a sunny day (
rains[i] == 0
):- Add the current day
i
tosunny
, which maintains a sorted list of days available for drying lakes. - Mark
ans[i]
as1
, which is a placeholder to show that we can dry any lake of our choice, although the actual choice is made on actual rainy days.
- Add the current day
- If it's a rainy day (
-
Return the Result: After processing all days in
rains
, return the constructedans
array. This array represents a sequence of decisions on each day to either observe rain (marked with-1
) or to dry a lake (marked with the lake index) in a way that avoids all potential floods.
The greedy component of our solution assumes that we should always use the closest sunny day after a previous rain to dry a lake. This minimizes the days that lakes remain full and maximizes the flexibility for managing future rains.
The SortedList data structure is crucial here for efficiently managing sunny days and using binary search to quickly find the optimal day to dry a lake. The decision-making process leverages the assumption that it's always best to delay decisions until they are necessary, which is typical in greedy algorithms. In essence, we're deferring the drying of lakes until it's clear that no other option will avoid a flood.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's take an example rains
array to understand the solution approach practically:
Suppose rains = [1, 2, 0, 0, 2, 1]
.
Here is how we would process this array:
-
Initialization:
ans = [-1, -1, -1, -1, -1, -1]
because we assume it's raining on every day to begin.sunny = SortedList()
to keep track of sunny days.rainy = {}
to track the last day it rained on each lake.
-
Day 1:
rains[0] = 1
- Lake 1 receives rain. We can't dry any lake today, so
ans[0] = -1
. - Update
rainy
to show the last rain day for lake 1:rainy[1] = 0
.
- Lake 1 receives rain. We can't dry any lake today, so
-
Day 2:
rains[1] = 2
- Lake 2 receives rain. We can't do anything, so
ans[1] = -1
. - Update
rainy
to show the last rain day for lake 2:rainy[2] = 1
.
- Lake 2 receives rain. We can't do anything, so
-
Day 3:
rains[2] = 0
- No rain today, we can dry a lake if necessary. Add day 3 to
sunny
:sunny.add(2)
. - Update
ans
for this first sunny day as a placeholder:ans[2] = 1
.
- No rain today, we can dry a lake if necessary. Add day 3 to
-
Day 4:
rains[3] = 0
- Another sunny day, we can dry another lake if necessary. Add day 4 to
sunny
:sunny.add(3)
. - Update
ans
for the second sunny day as a placeholder:ans[3] = 1
.
- Another sunny day, we can dry another lake if necessary. Add day 4 to
-
Day 5:
rains[4] = 2
- Rain on lake 2 once again. We need to prevent a flood.
- Look for the nearest sunny day after lake 2's last rain using
bisect_right
:bisect_right(sunny, rainy[2])
. The nearest day is day 3. - Update
ans[2]
to show lake 2 is dried on day 3:ans[2] = 2
. - Remove day 3 from
sunny
:sunny.remove(2)
. - Lake 2 is now filled with rain again, so
ans[4] = -1
and updaterainy
:rainy[2] = 4
.
-
Day 6:
rains[5] = 1
- It's raining again on lake 1. We then look for the nearest sunny day after lake 1's last rain:
bisect_right(sunny, rainy[1])
. The nearest day is day 4. - Dry lake 1 on day 4:
ans[3] = 1
, and remove day 4 fromsunny
:sunny.remove(3)
. - Record the new state of lake 1:
ans[5] = -1
, and updaterainy
:rainy[1] = 5
.
- It's raining again on lake 1. We then look for the nearest sunny day after lake 1's last rain:
Our final ans
array is [-1, -1, 2, 1, -1, -1]
, representing that we observed the rainfall on days when rains[i] > 0
and dried up lake 2 on the originally sunny day 3 (third position in ans
array) and lake 1 on originally sunny day 4 (fourth position in ans
array). This sequence ensures no lake overflow occurs.
The key element of this approach is the timely utilization of sunny days to dry the lakes that will receive more rain soon. We also need to ensure that we keep our records up to date at each step for both rainy and sunny days to maintain a correct and efficient process.
Solution Implementation
1from sortedcontainers import SortedList
2from typing import List
3
4class Solution:
5 def avoidFlood(self, rains: List[int]) -> List[int]:
6 # Length of the rains list
7 n = len(rains)
8
9 # Initialize an answer list with -1's representing days when lakes are filled
10 answers = [-1] * n
11
12 # Initialize a SortedList to keep track of sunny days
13 sunny_days = SortedList()
14
15 # Dictionary to keep track of the most recent day each lake was filled
16 recent_rainy_day = {}
17
18 # Iterate through the rain list
19 for day, lake in enumerate(rains):
20 # If the value is not zero, it's a rainy day
21 if lake:
22 # If the lake was filled before, we need to dry it before it rains again
23 if lake in recent_rainy_day:
24 # Find the first sunny day after the last rain on the same lake
25 index = sunny_days.bisect_right(recent_rainy_day[lake])
26
27 # If no such sunny day exists, we can't prevent the flood
28 if index == len(sunny_days):
29 return []
30
31 # Use the sunny day found to dry the lake
32 answers[sunny_days[index]] = lake
33
34 # Remove the used sunny day from the list
35 sunny_days.pop(index)
36
37 # Update the most recent day the lake was filled with rain
38 recent_rainy_day[lake] = day
39 # If the value is zero, it's a sunny day
40 else:
41 # Add the sunny day to the list
42 sunny_days.add(day)
43
44 # Placeholder value for sunny days, can be any number, set to 1
45 answers[day] = 1
46
47 # Return the filled answer list representing the actions taken each day
48 return answers
49
1class Solution {
2 public int[] avoidFlood(int[] rains) {
3 int n = rains.length;
4 // The result array is initialized with -1's (since 1...n are the lake indices)
5 int[] result = new int[n];
6 Arrays.fill(result, -1);
7
8 // A TreeSet to keep track of the days without rain (sunny days)
9 TreeSet<Integer> sunnyDays = new TreeSet<>();
10 // A map to keep the latest day when it rained on each lake
11 Map<Integer, Integer> lastRainDay = new HashMap<>();
12
13 for (int i = 0; i < n; ++i) {
14 int lake = rains[i];
15 if (lake > 0) {
16 // If a lake has been filled before, we need to dry it on a sunny day
17 if (lastRainDay.containsKey(lake)) {
18 // Find the next available sunny day to dry the lake
19 Integer dryingDay = sunnyDays.higher(lastRainDay.get(lake));
20 if (dryingDay == null) {
21 // If there's no sunny day after the last rain day, flooding is inevitable
22 return new int[0];
23 }
24 // Use the found sunny day to dry the lake
25 result[dryingDay] = lake;
26 // And remove that day from the set of available sunny days
27 sunnyDays.remove(dryingDay);
28 }
29 // Update the map with the last day it rained on the current lake
30 lastRainDay.put(lake, i);
31 } else {
32 // If there's no rain, we add this day to the set of sunny days
33 sunnyDays.add(i);
34 // Default drying action is 1, but this would be overridden if it's needed for a lake
35 result[i] = 1;
36 }
37 }
38 return result;
39 }
40}
41
1#include <vector>
2#include <set>
3#include <unordered_map>
4
5class Solution {
6public:
7 vector<int> avoidFlood(vector<int>& rains) {
8 int n = rains.size(); // Size of the rains vector
9 vector<int> answer(n, -1); // Initialize the answer vector with -1 (indicating that the lake is full on that day)
10 set<int> sunnyDays; // Stores the indices of days when it is sunny
11 unordered_map<int, int> fullLakes; // Maps a lake that is full to the last day it rained on it
12
13 // Iterate through the days
14 for (int i = 0; i < n; ++i) {
15 int lakeId = rains[i]; // The current lake or 0 for sunny day
16
17 if (lakeId) {
18 // If it rained on a lake
19 if (fullLakes.count(lakeId)) {
20 // If the lake is already full, find the next sunny day to empty it
21 auto it = sunnyDays.upper_bound(fullLakes[lakeId]); // Find the next sunny day after the last rain on this lake
22 if (it == sunnyDays.end()) {
23 // If there are no sunny days available to dry the lake, flooding is inevitable
24 return {};
25 }
26 answer[*it] = lakeId; // Use the sunny day to empty the lake
27 sunnyDays.erase(it); // Remove the used sunny day from the set
28 }
29 // Record the current day as the last rain day for the lake
30 fullLakes[lakeId] = i;
31 } else {
32 // If it is sunny, we have an opportunity to dry a lake
33 sunnyDays.insert(i); // Record this day as a sunny day
34 answer[i] = 1; // Default value for sunny days, can be any non-zero value since it will be replaced if we use this day to dry a lake
35 }
36 }
37
38 return answer;
39 }
40};
41
1// Represents a node in a Red-Black Tree.
2interface RBTreeNode<T = number> {
3 data: T;
4 count: number;
5 left: RBTreeNode<T> | null;
6 right: RBTreeNode<T> | null;
7 parent: RBTreeNode<T> | null;
8 color: number; // 0 for red, 1 for black
9}
10
11// Creates a new RBTreeNode with default properties.
12function createRBTreeNode<T>(data: T): RBTreeNode<T> {
13 return {
14 data,
15 count: 1,
16 left: null,
17 right: null,
18 parent: null,
19 color: 0
20 };
21}
22
23// Utility function to check if a given node is on the left side of its parent.
24function isOnLeft<T>(node: RBTreeNode<T>): boolean {
25 return node === node.parent?.left;
26}
27
28// Utility function for swapping the colors of two nodes.
29function swapColor<T>(node1: RBTreeNode<T>, node2: RBTreeNode<T>): void {
30 const temp = node1.color;
31 node1.color = node2.color;
32 node2.color = temp;
33}
34
35// Many other functions and utilities would follow similar to the class methods seen in the original code.
36// Here the `avoidFlood` function is adapted to use global variables and functions.
37
38// Defines a compare function type for comparing generic types.
39type Compare<T> = (a: T, b: T) => number;
40
41// Main `avoidFlood` function: Solves the avoid flood problem leveraging a TreeSet structure.
42function avoidFlood(rains: number[]): number[] {
43 const n = rains.length;
44 const ans: number[] = new Array(n).fill(-1);
45 const sunnyDays = createTreeSet<number>();
46 const fullLakes = new Map<number, number>();
47
48 for (let i = 0; i < n; ++i) {
49 const lake = rains[i];
50 if (lake > 0) {
51 if (fullLakes.has(lake)) {
52 const dryDayIndex = higher(sunnyDays, fullLakes.get(lake)!);
53 if (dryDayIndex === undefined) {
54 return []; // No solution possible, bail out.
55 }
56 ans[dryDayIndex] = lake;
57 deleteFromSet(sunnyDays, dryDayIndex);
58 }
59 fullLakes.set(lake, i);
60 } else {
61 addToSet(sunnyDays, i);
62 ans[i] = 1;
63 }
64 }
65 return ans;
66}
67
68// Global variables and methods that correspond to the `TreeSet` and `RBTree` functionality
69// would need to be defined here, such as `addToSet`, `deleteFromSet`, `higher`, `rotateLeft`, etc.
70
71// Additional utility functions for TreeSet and RBTree operations would go below...
72
Time and Space Complexity
Time Complexity
The time complexity of the solution is O(n * log n)
. This complexity arises due to the following reasons:
- We iterate over all
n
elements in therains
list. - For each rainy day (non-zero element within
rains
), we userainy[v] = i
to store the last day a lake was filled which isO(1)
for each operation. - For each sunny day (zero element within
rains
), we usesunny.add(i)
which is anO(log n)
operation sinceSortedList()
maintains a sorted order. - When we need to find a sunny day to dry a lake, we perform
sunny.bisect_right(rainy[v])
which is anO(log n)
operation since it is a binary search within the sorted list of sunny days. - We then remove the used sunny day slot with
sunny.discard(sunny[idx])
. This operation isO(log n)
as well, since removal from aSortedList
requires a search for the element's index followed by the removal, both contributing to thelog n
complexity.
Since these operations take place within a loop that runs n
times, the overall time complexity combines to O(n * log n)
.
Space Complexity
The space complexity of the solution is O(n)
. This is because:
- We maintain an
ans
list of sizen
. - We have a
SortedList
namedsunny
which could potentially, in the worst case, hold a separate entry for every day which would also beO(n)
. - The
rainy
dictionary in the worst case will store an entry for every lake that gets filled which is bound by the number of days, so it is alsoO(n)
space complexity.
Thus, the maximum of these space complexities dictates the overall space complexity, which is O(n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
In a binary min heap, the maximum element can be found in:
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
https algomonster s3 us east 2 amazonaws com cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue
Want a Structured Path to Master System Design Too? Don’t Miss This!