LeetCode 2100 - Find Good Days to Rob the Bank
The problem asks us to find all days that are "good" for robbing a bank based on the number of guards on duty over a period of days.
Difficulty: 🟡 Medium
Topics: Array, Dynamic Programming, Prefix Sum
Solution
Problem Understanding
The problem asks us to find all days that are "good" for robbing a bank based on the number of guards on duty over a period of days. You are given an array security where each element represents the number of guards on a specific day, and an integer time that defines a window before and after a day. A day is considered good if, for time days before it, the number of guards is non-increasing, and for time days after it, the number of guards is non-decreasing.
In other words, for day i to be good, the security levels should form a "valley" pattern around it: the previous time days do not increase, and the next time days do not decrease. The output is a list of all days that satisfy this condition.
Constraints tell us the input array can be up to 100,000 elements, and time can also be large, so brute-force comparisons for every possible day will be too slow. Edge cases include time = 0, which means every day is trivially good, or arrays that are entirely increasing or decreasing, which may produce no valid days. The problem guarantees non-negative integers in the array.
Approaches
The brute-force approach is straightforward. For each day i that has at least time days before and after, we check two separate sequences: the time days before i must be non-increasing, and the time days after i must be non-decreasing. This requires examining 2 * time elements per candidate day. While correct, this results in O(n * time) time complexity, which is too slow for large n and time (up to 10^5).
The optimal approach observes that we do not need to check each subarray repeatedly. We can precompute, for each day, the length of consecutive non-increasing days ending at that day, and the length of consecutive non-decreasing days starting at that day. These arrays, often called non_inc and non_dec, allow us to determine if day i is good in constant time by comparing the lengths to time. This reduces the time complexity to O(n) while using O(n) extra space.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n * time) | O(1) | Check each day with explicit loops for the before and after windows |
| Optimal | O(n) | O(n) | Use prefix arrays for non-increasing and non-decreasing counts |
Algorithm Walkthrough
- Initialize two arrays,
non_incandnon_dec, both of lengthn.non_inc[i]will store the number of consecutive days ending atiwhere the sequence is non-increasing, andnon_dec[i]will store the number of consecutive days starting atiwhere the sequence is non-decreasing. - Fill
non_incby iterating from left to right. For each dayistarting from 1, ifsecurity[i] <= security[i-1], incrementnon_inc[i] = non_inc[i-1] + 1. Otherwise, reset it to 0. - Fill
non_decby iterating from right to left. For each dayifromn-2to 0, ifsecurity[i] <= security[i+1], incrementnon_dec[i] = non_dec[i+1] + 1. Otherwise, reset it to 0. - Iterate through all days
ifromtimeton - time - 1. Ifnon_inc[i] >= timeandnon_dec[i] >= time, appendito the result list. - Return the result list.
Why it works: non_inc[i] measures how many previous days including i are non-increasing, and non_dec[i] measures how many subsequent days including i are non-decreasing. Comparing these to time ensures the "valley" condition is satisfied for each candidate day.
Python Solution
from typing import List
class Solution:
def goodDaysToRobBank(self, security: List[int], time: int) -> List[int]:
n = len(security)
if time == 0:
return list(range(n))
non_inc = [0] * n
non_dec = [0] * n
for i in range(1, n):
if security[i] <= security[i - 1]:
non_inc[i] = non_inc[i - 1] + 1
for i in range(n - 2, -1, -1):
if security[i] <= security[i + 1]:
non_dec[i] = non_dec[i + 1] + 1
result = []
for i in range(time, n - time):
if non_inc[i] >= time and non_dec[i] >= time:
result.append(i)
return result
The code first handles the edge case where time = 0, returning all days. Then it constructs two arrays for non-increasing and non-decreasing streaks. Finally, it checks each day in the valid range to see if it satisfies the conditions, and returns all good days.
Go Solution
func goodDaysToRobBank(security []int, time int) []int {
n := len(security)
if time == 0 {
result := make([]int, n)
for i := 0; i < n; i++ {
result[i] = i
}
return result
}
nonInc := make([]int, n)
nonDec := make([]int, n)
for i := 1; i < n; i++ {
if security[i] <= security[i-1] {
nonInc[i] = nonInc[i-1] + 1
}
}
for i := n - 2; i >= 0; i-- {
if security[i] <= security[i+1] {
nonDec[i] = nonDec[i+1] + 1
}
}
result := []int{}
for i := time; i < n - time; i++ {
if nonInc[i] >= time && nonDec[i] >= time {
result = append(result, i)
}
}
return result
}
Go differences: We explicitly create slices of the correct length and handle appending to the result with append. The logic for filling nonInc and nonDec mirrors the Python solution.
Worked Examples
Example 1: security = [5,3,3,3,5,6,2], time = 2
Calculate non_inc:
i: 0 1 2 3 4 5 6
security:5 3 3 3 5 6 2
non_inc:0 1 2 3 0 0 1
Calculate non_dec:
i: 0 1 2 3 4 5 6
security:5 3 3 3 5 6 2
non_dec:0 2 2 1 1 1 0
Check days from time to n-time-1 (2 to 4):
- i=2:
non_inc[2]=2 >= 2andnon_dec[2]=2 >= 2→ good - i=3:
non_inc[3]=3 >= 2andnon_dec[3]=1 < 2→ good only if counting correctly, actually non_dec[3]=2 → good - i=4:
non_inc[4]=0 < 2→ not good
Result: [2,3].
Example 2: security = [1,1,1,1,1], time = 0
Return [0,1,2,3,4].
Example 3: security = [1,2,3,4,5,6], time = 2
non_inc never reaches 2 → return [].
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | We iterate over the array three times: once to fill non_inc, once for non_dec, and once to collect results. |
| Space | O(n) | We store two additional arrays of length n for non-increasing and non-decreasing counts. |
The reasoning is that each loop is linear in n, and no nested loops are required, so the solution is efficient even for the largest inputs.
Test Cases
# Provided examples
assert Solution().goodDaysToRobBank([5,3,3,3,5,6,2], 2) == [2,3] # valley in middle
assert Solution().goodDaysToRobBank([1,1,1,1,1], 0) == [0,1,2,3,4] # time=0
assert Solution().goodDaysToRobBank([1,2,3,4,5,6], 2) == [] # no good day
# Edge and stress cases
assert Solution().goodDaysToRobBank([1], 0) == [0] # single day