Facebook Pixel

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:

  1. Sufficient buffer days: There must be at least time days before and after day i. This means i >= time and i + time < n (where n is the length of the array).

  2. Non-increasing pattern before: The number of guards for time days leading up to day i should be non-increasing. In other words: security[i - time] >= security[i - time + 1] >= ... >= security[i - 1] >= security[i].

  3. Non-decreasing pattern after: The number of guards for time days following day i 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 position i
  • right[i]: Tracks the length of the non-decreasing sequence starting at position i

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)
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 size n

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example 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:
    1. First loop from index 1 to n-1 to populate the left array: O(n)
    2. Second loop from index n-2 to 0 to populate the right array: O(n)
    3. List comprehension to build the result by checking each index: O(n)
  • 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 and right are created, each of size n: 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 position i
  • right[i] counts the total non-decreasing sequence including position i

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 before i form a non-increasing sequence
  • right[i]: How many consecutive days after i 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 day i
  • Day i itself
  • time days after day i 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
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More