AlgoMaster Logo

Avoid Flood in the City

mediumFrequencyUpdated September 21, 2026

Understanding the Problem

This is a scheduling problem. On rainy days, lakes fill and we have no control. On dry days, we dry exactly one lake of our choice. The goal is to assign dry days so that no lake is rained on while it is still full.

The difficulty is that a good drying decision depends on the future. If lakes 1 and 2 are both full and lake 2 rains again sooner, the next dry day should go to lake 2. A left-to-right pass does not know that at the moment the dry day arrives, so either the decision has to be deferred until the future reveals itself, or the future has to be precomputed.

A lake only needs drying if it rains again, and what saves it is a dry day strictly between its two rain events. The problem reduces to matching each repeat rain with an unused dry day in that window.

Key Constraints:

  • 1 <= rains.length <= 10^5 → With up to 100,000 days, we need O(n log n) or better. An O(n^2) approach that searches for dry days linearly for each rain event would be too slow.
  • 0 <= rains[i] <= 10^9 → Lake numbers can be as large as 10^9, so an array indexed by lake number is not an option. Lake bookkeeping needs a hash map.

Approach 1: Brute Force (Linear Search for Dry Days)

Intuition

Process days left to right. A hash map records the most recent rain day for each lake, which doubles as a record of which lakes are full. A list collects the indices of dry days seen so far. When rain falls on a lake that is already full, scan the dry-day list for an index after the lake's previous rain day and spend that dry day on this lake.

The linear scan through the dry-day list is the expensive part: every repeat rain may walk the whole list.

Algorithm

  1. Initialize a hash map lakeDay to record when each lake last rained.
  2. Initialize a list dryDays to store indices of dry days encountered so far.
  3. Create the result array; every position is assigned during the pass.
  4. For each day i:
    • If rains[i] == 0, add i to dryDays. Set ans[i] = 1 as a placeholder (we must output a valid lake number for dry days, even if we have not decided yet).
    • If rains[i] > 0 (let lake = rains[i]):
      • If lake is not in lakeDay, it is not full. Record lakeDay[lake] = i. Set ans[i] = -1.
      • If lake is already in lakeDay (it is full), search dryDays for any index d where d > lakeDay[lake]. If found, remove d from dryDays, set ans[d] = lake, and update lakeDay[lake] = i. If not found, return empty array.
  5. Return the result array.

Visualization and Code

Loading animation...

Dry days are appended in increasing index order, so the collection is already sorted. A structure with an ordered ceiling query replaces the linear scan with a logarithmic one.

Approach 2: Greedy with Sorted Set

Intuition

When rain falls on an already-full lake, the algorithm needs the earliest unused dry day after that lake's previous rain. "Smallest element greater than X" is a ceiling query, and a sorted set answers it in O(log n).

So the strategy becomes:

  • Collect dry day indices in a sorted set.
  • Track each lake's most recent rain day in a hash map.
  • When a lake rains again, query the sorted set for the smallest dry day index greater than the lake's previous rain day.
  • If found, use that dry day to dry this lake. If not found, it is impossible.

A dry day can only be spent on a lake whose previous rain came before it, so later dry days are usable by more future lakes. When several dry days are valid, spending the earliest one preserves the more widely usable ones.

Algorithm

  1. Initialize a hash map lakeDay to record when each lake last rained.
  2. Initialize a sorted set drySet to store indices of available dry days.
  3. Create the result array; every position is assigned during the pass.
  4. For each day i:
    • If rains[i] == 0, add i to drySet. Set ans[i] = 1 as a placeholder.
    • If rains[i] > 0 (let lake = rains[i]):
      • If lake is not in lakeDay, record lakeDay[lake] = i. Set ans[i] = -1.
      • If lake is in lakeDay (it is full):
        • Query drySet for the ceiling of lakeDay[lake] + 1 (the smallest dry day index strictly after the previous rain day).
        • If no such dry day exists, return an empty array.
        • Otherwise, remove that dry day from drySet, set ans[dryDay] = lake, and update lakeDay[lake] = i. Set ans[i] = -1.
  5. Return the result array.

Visualization and Code

Loading animation...

The sorted set resolves floods reactively: nothing happens until a lake is about to overflow, and only then does the algorithm look back for an unused dry day. The same scheduling can run forward instead. On each dry day, decide on the spot which full lake to dry: the one that rains again soonest.

Approach 3: Next-Rain Precomputation with Min-Heap

Intuition

Deciding on a dry day requires knowing when each full lake rains next. One right-to-left pass computes that in advance: for every rain day i, record nextRain[i], the next day on which the same lake rains, or n if it never rains again.

With that table the forward pass becomes deadline scheduling. Every full lake that rains again must be dried before its next rain, and each dry day is one scheduling slot. On a rain day, the lake fills; if it will rain again, push the pair (next rain day, lake) onto a min-heap keyed by the deadline. On a dry day, pop the lake with the earliest deadline and dry it. If the heap is empty, no full lake ever rains again, so the choice does not matter and any lake number works as a placeholder. If rain falls on a lake that is still full, return an empty array.

The heap never holds stale entries. An entry is pushed when a lake fills and popped when the lake is dried. If an entry is still in the heap when its deadline arrives, that rain hits a full lake and the function returns immediately, so every pop yields a currently full lake.

Algorithm

  1. Build nextRain with a right-to-left pass: nextRain[i] is the next index after i on which lake rains[i] rains again, or n if there is none. A hash map from lake to its most recently seen index supplies each lookup.
  2. Initialize a hash set full of currently full lakes and an empty min-heap of (next rain day, lake) pairs.
  3. For each day i:
    • If rains[i] > 0 (let lake = rains[i]): if lake is in full, return an empty array. Otherwise add lake to full, push (nextRain[i], lake) onto the heap if nextRain[i] < n, and set ans[i] = -1.
    • If rains[i] == 0: if the heap is empty, set ans[i] = 1 as a placeholder. Otherwise pop the pair with the smallest next rain day, remove that lake from full, and record the lake in ans[i].
  4. Return the result array.

Visualization and Code

Loading animation...