2100. Find Good Days to Rob the Bank
Problem Description
You're given an array security
where security[i]
represents the number of guards on duty on day i
. You need to find all the "good days" to rob a bank based on specific conditions.
A day i
is considered a good day to rob the bank if:
-
Sufficient buffer days: There must be at least
time
days before and after dayi
. This meansi >= time
andi + time < n
(wheren
is the length of the array). -
Non-increasing pattern before: The number of guards for
time
days leading up to dayi
should be non-increasing. In other words:security[i - time] >= security[i - time + 1] >= ... >= security[i - 1] >= security[i]
. -
Non-decreasing pattern after: The number of guards for
time
days following dayi
should be non-decreasing. In other words:security[i] <= security[i + 1] <= ... <= security[i + time - 1] <= security[i + time]
.
The solution uses a preprocessing approach with two arrays:
left[i]
: Tracks the length of the non-increasing sequence ending at positioni
right[i]
: Tracks the length of the non-decreasing sequence starting at positioni
For each position i
, if both left[i] >= time
and right[i] >= time
, then day i
satisfies all conditions and is a good day to rob the bank.
The function returns a list of all such good days (0-indexed). The order of days in the result doesn't matter.
Example: If security = [5,3,3,3,5,6,2]
and time = 2
, day 2 and day 3 would be good days because:
- Day 2: The pattern
[5,3,3]
before (non-increasing) and[3,3,5]
after (non-decreasing) - Day 3: The pattern
[3,3,3]
before (non-increasing) and[3,5,6]
after (non-decreasing)
Intuition
The naive approach would be to check each day i
by examining time
days before and after it, verifying if the guards follow the required pattern. However, this would result in O(n * time)
complexity, which can be inefficient.
The key insight is recognizing that we're looking for days that sit at the "valley" of a specific pattern - where the number of guards decreases (or stays same) leading up to that day, then increases (or stays same) after that day.
Instead of repeatedly checking the same sequences for different days, we can precompute information about monotonic sequences. Consider this: if we know that days 0-4 form a non-increasing sequence, then day 4 can look back 4 days of non-increasing pattern. Similarly, if days 4-7 form a non-decreasing sequence, then day 4 can look forward 3 days of non-decreasing pattern.
This leads us to the preprocessing approach:
- For each position, we calculate how many consecutive days before it form a non-increasing sequence (
left
array) - For each position, we calculate how many consecutive days after it form a non-decreasing sequence (
right
array)
Building left[i]
: If security[i] <= security[i-1]
, then the non-increasing sequence extends, so left[i] = left[i-1] + 1
. Otherwise, the sequence breaks and left[i] = 0
.
Building right[i]
: We traverse backwards. If security[i] <= security[i+1]
, then this position starts a non-decreasing sequence that extends to the right, so right[i] = right[i+1] + 1
.
Once we have these arrays, checking if day i
is good becomes simple: we just need left[i] >= time
and right[i] >= time
. This ensures there are enough non-increasing days before and enough non-decreasing days after day i
.
This optimization reduces our time complexity to O(n)
with O(n)
extra space - a significant improvement over the naive approach.
Learn more about Dynamic Programming and Prefix Sum patterns.
Solution Approach
The implementation follows a three-phase approach: preprocessing left trends, preprocessing right trends, and finding valid days.
Phase 1: Build the left
array
We create a left
array where left[i]
represents the number of consecutive non-increasing days ending at position i
.
left = [0] * n
for i in range(1, n):
if security[i] <= security[i - 1]:
left[i] = left[i - 1] + 1
Starting from index 1, we check if the current day has guards less than or equal to the previous day. If yes, we extend the non-increasing sequence count by adding 1 to the previous count. Otherwise, left[i]
remains 0 (sequence breaks).
Phase 2: Build the right
array
We create a right
array where right[i]
represents the number of consecutive non-decreasing days starting at position i
.
right = [0] * n
for i in range(n - 2, -1, -1):
if security[i] <= security[i + 1]:
right[i] = right[i + 1] + 1
We traverse backwards from index n-2
to 0. If the current day has guards less than or equal to the next day, we extend the non-decreasing sequence count. This backward traversal is crucial because we're counting days that come after position i
.
Phase 3: Find good days
return [i for i in range(n) if time <= min(left[i], right[i])]
For each day i
, we check if both left[i] >= time
and right[i] >= time
. This can be simplified to checking if time <= min(left[i], right[i])
. If this condition holds, day i
has at least time
non-increasing days before it and at least time
non-decreasing days after it, making it a good day to rob the bank.
Edge Case Handling
The initial check if n <= time * 2: return []
handles the case where the array is too small to have any valid days. If we need time
days before and time
days after any day i
, the minimum array length must be 2 * time + 1
.
Time and Space Complexity
- Time:
O(n)
- We make three linear passes through the array - Space:
O(n)
- We use two auxiliary arrays of sizen
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with security = [5,3,3,3,5,6,2]
and time = 2
.
Step 1: Build the left
array (non-increasing sequences ending at each position)
Starting with left = [0, 0, 0, 0, 0, 0, 0]
:
- Index 0: Base case,
left[0] = 0
- Index 1:
security[1]=3 <= security[0]=5
? Yes →left[1] = left[0] + 1 = 1
- Index 2:
security[2]=3 <= security[1]=3
? Yes →left[2] = left[1] + 1 = 2
- Index 3:
security[3]=3 <= security[2]=3
? Yes →left[3] = left[2] + 1 = 3
- Index 4:
security[4]=5 <= security[3]=3
? No →left[4] = 0
- Index 5:
security[5]=6 <= security[4]=5
? No →left[5] = 0
- Index 6:
security[6]=2 <= security[5]=6
? Yes →left[6] = left[5] + 1 = 1
Result: left = [0, 1, 2, 3, 0, 0, 1]
Step 2: Build the right
array (non-decreasing sequences starting at each position)
Starting with right = [0, 0, 0, 0, 0, 0, 0]
:
- Index 6: Base case,
right[6] = 0
- Index 5:
security[5]=6 <= security[6]=2
? No →right[5] = 0
- Index 4:
security[4]=5 <= security[5]=6
? Yes →right[4] = right[5] + 1 = 1
- Index 3:
security[3]=3 <= security[4]=5
? Yes →right[3] = right[4] + 1 = 2
- Index 2:
security[2]=3 <= security[3]=3
? Yes →right[2] = right[3] + 1 = 3
- Index 1:
security[1]=3 <= security[2]=3
? Yes →right[1] = right[2] + 1 = 4
- Index 0:
security[0]=5 <= security[1]=3
? No →right[0] = 0
Result: right = [0, 4, 3, 2, 1, 0, 0]
Step 3: Find good days where min(left[i], right[i]) >= time = 2
Check each index:
- Index 0:
min(0, 0) = 0 < 2
❌ - Index 1:
min(1, 4) = 1 < 2
❌ - Index 2:
min(2, 3) = 2 >= 2
✓ - Index 3:
min(3, 2) = 2 >= 2
✓ - Index 4:
min(0, 1) = 0 < 2
❌ - Index 5:
min(0, 0) = 0 < 2
❌ - Index 6:
min(1, 0) = 0 < 2
❌
Result: Days 2 and 3 are good days to rob the bank
Verification:
- Day 2: Has 2 non-increasing days before (5→3→3) and 2 non-decreasing days after (3→5→6)
- Day 3: Has 2 non-increasing days before (3→3→3) and 2 non-decreasing days after (3→5→6)
Solution Implementation
1class Solution:
2 def goodDaysToRobBank(self, security: List[int], time: int) -> List[int]:
3 """
4 Find all days that are good for robbing the bank.
5 A day i is good if there are at least 'time' days before and after it
6 where the security is non-increasing before and non-decreasing after.
7
8 Args:
9 security: List of integers representing security guard count each day
10 time: Minimum number of days required before and after the robbery day
11
12 Returns:
13 List of indices representing good days to rob the bank
14 """
15 n = len(security)
16
17 # If array is too small to have 'time' days before and after, return empty
18 if n <= time * 2:
19 return []
20
21 # left[i]: count of consecutive non-increasing days ending at day i
22 # right[i]: count of consecutive non-decreasing days starting at day i
23 left = [0] * n
24 right = [0] * n
25
26 # Build left array: count consecutive non-increasing days from left
27 for i in range(1, n):
28 if security[i] <= security[i - 1]:
29 left[i] = left[i - 1] + 1
30 else:
31 left[i] = 0
32
33 # Build right array: count consecutive non-decreasing days from right
34 for i in range(n - 2, -1, -1):
35 if security[i] <= security[i + 1]:
36 right[i] = right[i + 1] + 1
37 else:
38 right[i] = 0
39
40 # Find all days where both conditions are satisfied
41 # Day i is good if it has at least 'time' non-increasing days before
42 # and at least 'time' non-decreasing days after
43 result = []
44 for i in range(n):
45 if left[i] >= time and right[i] >= time:
46 result.append(i)
47
48 return result
49
1class Solution {
2 public List<Integer> goodDaysToRobBank(int[] security, int time) {
3 int n = security.length;
4
5 // If array is too small to have valid days, return empty list
6 if (n <= time * 2) {
7 return Collections.emptyList();
8 }
9
10 // Array to store count of consecutive non-increasing days before each index
11 int[] nonIncreasingDaysBefore = new int[n];
12
13 // Array to store count of consecutive non-decreasing days after each index
14 int[] nonDecreasingDaysAfter = new int[n];
15
16 // Calculate consecutive non-increasing days before each position
17 // Starting from index 1, check if current element is <= previous element
18 for (int i = 1; i < n; i++) {
19 if (security[i] <= security[i - 1]) {
20 nonIncreasingDaysBefore[i] = nonIncreasingDaysBefore[i - 1] + 1;
21 }
22 }
23
24 // Calculate consecutive non-decreasing days after each position
25 // Starting from second-to-last index, check if current element is <= next element
26 for (int i = n - 2; i >= 0; i--) {
27 if (security[i] <= security[i + 1]) {
28 nonDecreasingDaysAfter[i] = nonDecreasingDaysAfter[i + 1] + 1;
29 }
30 }
31
32 // Collect all valid days to rob the bank
33 List<Integer> validDays = new ArrayList<>();
34
35 // Check each potential day (must have at least 'time' days before and after)
36 for (int i = time; i < n - time; i++) {
37 // Day i is valid if it has at least 'time' non-increasing days before
38 // AND at least 'time' non-decreasing days after
39 if (time <= Math.min(nonIncreasingDaysBefore[i], nonDecreasingDaysAfter[i])) {
40 validDays.add(i);
41 }
42 }
43
44 return validDays;
45 }
46}
47
1class Solution {
2public:
3 vector<int> goodDaysToRobBank(vector<int>& security, int time) {
4 int n = security.size();
5
6 // Early return if the array is too small to have valid days
7 if (n <= time * 2) {
8 return {};
9 }
10
11 // left[i]: number of consecutive non-increasing days ending at index i
12 vector<int> left(n, 0);
13
14 // right[i]: number of consecutive non-decreasing days starting at index i
15 vector<int> right(n, 0);
16
17 // Calculate non-increasing streak from left to right
18 for (int i = 1; i < n; ++i) {
19 if (security[i] <= security[i - 1]) {
20 left[i] = left[i - 1] + 1;
21 }
22 }
23
24 // Calculate non-decreasing streak from right to left
25 for (int i = n - 2; i >= 0; --i) {
26 if (security[i] <= security[i + 1]) {
27 right[i] = right[i + 1] + 1;
28 }
29 }
30
31 // Collect all valid days to rob the bank
32 vector<int> result;
33 for (int i = time; i < n - time; ++i) {
34 // Check if day i has at least 'time' non-increasing days before
35 // and at least 'time' non-decreasing days after
36 if (left[i] >= time && right[i] >= time) {
37 result.push_back(i);
38 }
39 }
40
41 return result;
42 }
43};
44
1/**
2 * Finds all good days to rob the bank based on security guard patterns
3 * A day is good if there are at least 'time' non-increasing days before it
4 * and at least 'time' non-decreasing days after it
5 *
6 * @param security - Array of security guard counts for each day
7 * @param time - Minimum number of days required before and after
8 * @returns Array of indices representing good days to rob the bank
9 */
10function goodDaysToRobBank(security: number[], time: number): number[] {
11 const totalDays: number = security.length;
12
13 // If there aren't enough days to satisfy the time constraints, return empty
14 if (totalDays <= time * 2) {
15 return [];
16 }
17
18 // Track consecutive non-increasing days ending at each position
19 const nonIncreasingDays: number[] = new Array(totalDays).fill(0);
20
21 // Track consecutive non-decreasing days starting at each position
22 const nonDecreasingDays: number[] = new Array(totalDays).fill(0);
23
24 // Build the arrays in a single pass
25 for (let i = 1; i < totalDays; i++) {
26 // Count consecutive non-increasing days (looking backward)
27 if (security[i] <= security[i - 1]) {
28 nonIncreasingDays[i] = nonIncreasingDays[i - 1] + 1;
29 }
30
31 // Count consecutive non-decreasing days (looking forward from the end)
32 const reverseIndex: number = totalDays - i - 1;
33 if (security[reverseIndex] <= security[reverseIndex + 1]) {
34 nonDecreasingDays[reverseIndex] = nonDecreasingDays[reverseIndex + 1] + 1;
35 }
36 }
37
38 // Collect all valid days that satisfy both conditions
39 const goodDays: number[] = [];
40
41 // Check each potential day (must have 'time' days before and after)
42 for (let day = time; day < totalDays - time; day++) {
43 // Check if this day has enough non-increasing days before
44 // and non-decreasing days after
45 if (time <= Math.min(nonIncreasingDays[day], nonDecreasingDays[day])) {
46 goodDays.push(day);
47 }
48 }
49
50 return goodDays;
51}
52
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the security array.
- The algorithm makes three linear passes through the array:
- First loop from index
1
ton-1
to populate theleft
array:O(n)
- Second loop from index
n-2
to0
to populate theright
array:O(n)
- List comprehension to build the result by checking each index:
O(n)
- First loop from index
- Each operation within the loops takes constant time
O(1)
- Total time complexity:
O(n) + O(n) + O(n) = O(n)
Space Complexity: O(n)
, where n
is the length of the security array.
- Two auxiliary arrays
left
andright
are created, each of sizen
:O(n) + O(n) = O(2n) = O(n)
- The output list in the worst case (when all days are good days to rob) can contain up to
n
elements:O(n)
- Total space complexity:
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error in Sequence Counting
The Pitfall:
A common mistake is misunderstanding what left[i]
and right[i]
represent. Developers often incorrectly assume:
left[i]
counts the total non-increasing sequence including positioni
right[i]
counts the total non-decreasing sequence including positioni
This leads to incorrect implementations like:
# WRONG: This counts the current position in the sequence
for i in range(1, n):
if security[i] <= security[i - 1]:
left[i] = left[i - 1] + 1
else:
left[i] = 1 # Wrong! Should be 0
Why It's Wrong:
The problem requires exactly time
days BEFORE and AFTER day i
, not including day i
itself. The arrays should count:
left[i]
: How many consecutive days beforei
form a non-increasing sequenceright[i]
: How many consecutive days afteri
form a non-decreasing sequence
The Solution: Initialize both arrays with 0 and only increment when extending a valid sequence:
left = [0] * n # Correctly initialized to 0
right = [0] * n
for i in range(1, n):
if security[i] <= security[i - 1]:
left[i] = left[i - 1] + 1
# No else clause needed; left[i] stays 0 if sequence breaks
2. Incorrect Boundary Check
The Pitfall: Using the wrong formula for checking if there's enough space:
# WRONG: This allows arrays that are too small if n < time * 2: # Missing the day i itself return []
Why It's Wrong: We need:
time
days before dayi
- Day
i
itself time
days after dayi
Total minimum length =time + 1 + time = 2 * time + 1
The Solution: Use the correct boundary check:
if n <= time * 2: # Correct: n must be at least 2*time + 1 return []
3. Confusing Non-Increasing vs. Strictly Decreasing
The Pitfall: Using strict inequality when the problem requires non-increasing (allowing equal values):
# WRONG: Using strict inequality if security[i] < security[i - 1]: # Should be <= left[i] = left[i - 1] + 1
Why It's Wrong:
The problem specifically states "non-increasing" which means values can be equal. The sequence [5, 3, 3, 3]
is valid non-increasing, but using <
would break at the equal values.
The Solution:
Always use <=
for both left and right array construction:
# Correct: non-increasing allows equal values if security[i] <= security[i - 1]: left[i] = left[i - 1] + 1 # Correct: non-decreasing allows equal values if security[i] <= security[i + 1]: right[i] = right[i + 1] + 1
4. Wrong Direction for Right Array Traversal
The Pitfall:
Building the right
array from left to right:
# WRONG: Cannot determine future trend going forward
for i in range(n - 1):
if security[i] <= security[i + 1]:
right[i] = right[i - 1] + 1 # Wrong logic
Why It's Wrong:
To know how many non-decreasing days follow position i
, we must traverse backwards. Going forward, we can't know what comes next.
The Solution:
Always traverse backwards for the right
array:
# Correct: Build right array from right to left
for i in range(n - 2, -1, -1):
if security[i] <= security[i + 1]:
right[i] = right[i + 1] + 1
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!