2420. Find All Good Indices
Problem Description
You have an array nums
of integers (0-indexed) with size n
, and a positive integer k
.
An index i
is considered good if it meets these conditions:
- The index must be in the range
k <= i < n - k
(meaning there are at leastk
elements before and after it) - The
k
elements immediately before indexi
(from positioni-k
toi-1
) must be in non-increasing order (each element is less than or equal to the previous one) - The
k
elements immediately after indexi
(from positioni+1
toi+k
) must be in non-decreasing order (each element is greater than or equal to the previous one)
Your task is to find all good indices and return them as an array sorted in increasing order.
For example, if we have nums = [2,1,1,1,3,4,1]
and k = 2
:
- Index 2 would be good if the 2 elements before it
[2,1]
are non-increasing (✓) and the 2 elements after it[1,3]
are non-decreasing (✓) - We check each valid index in the range to determine if it's good
The solution uses dynamic programming to track:
decr[i]
: the length of the longest non-increasing sequence ending just before indexi
incr[i]
: the length of the longest non-decreasing sequence starting just after indexi
An index i
is good when both decr[i] >= k
and incr[i] >= k
, meaning there are at least k
elements in the required order on both sides.
Intuition
The key insight is that checking each index individually by examining k
elements before and after would be inefficient, requiring O(n*k)
time. Instead, we can precompute information about the ordering patterns throughout the array.
Think about what makes an index "good" - we need to know if there are at least k
consecutive elements in non-increasing order before it, and at least k
consecutive elements in non-decreasing order after it. Rather than checking this repeatedly for each index, we can build this information once for the entire array.
The crucial observation is that if we know the length of the longest non-increasing sequence ending at position i-1
, we can determine if index i
has at least k
non-increasing elements before it. Similarly, if we know the length of the longest non-decreasing sequence starting at position i+1
, we can determine if index i
has at least k
non-decreasing elements after it.
This leads us to use dynamic programming:
- For the non-increasing sequences before each index: we traverse left to right. If
nums[i-1] <= nums[i-2]
, then the non-increasing sequence continues, sodecr[i] = decr[i-1] + 1
. Otherwise, it breaks and we reset to 1. - For the non-decreasing sequences after each index: we traverse right to left. If
nums[i+1] <= nums[i+2]
, then the non-decreasing sequence continues, soincr[i] = incr[i+1] + 1
. Otherwise, it breaks and we reset to 1.
Once we've built these arrays in O(n)
time, checking if an index is good becomes an O(1)
operation - just verify if decr[i] >= k
and incr[i] >= k
. This reduces the overall complexity from O(n*k)
to O(n)
.
Learn more about Dynamic Programming and Prefix Sum patterns.
Solution Approach
The solution uses dynamic programming with two auxiliary arrays to track the non-increasing and non-decreasing sequences.
Step 1: Initialize the arrays
- Create
decr
array of sizen+1
initialized with 1s to track the length of non-increasing sequences ending just before each index - Create
incr
array of sizen+1
initialized with 1s to track the length of non-decreasing sequences starting just after each index
Step 2: Build the decr
array (left to right)
for i in range(2, n - 1):
if nums[i - 1] <= nums[i - 2]:
decr[i] = decr[i - 1] + 1
- Start from index 2 (since we need to check
nums[i-1]
andnums[i-2]
) - If
nums[i-1] <= nums[i-2]
, the non-increasing pattern continues, so we extend the previous length by 1 - Otherwise,
decr[i]
remains 1 (the sequence breaks) decr[i]
represents how many consecutive non-increasing elements exist just before indexi
Step 3: Build the incr
array (right to left)
for i in range(n - 3, -1, -1):
if nums[i + 1] <= nums[i + 2]:
incr[i] = incr[i + 1] + 1
- Start from index
n-3
going backwards (since we need to checknums[i+1]
andnums[i+2]
) - If
nums[i+1] <= nums[i+2]
, the non-decreasing pattern continues, so we extend the length from the right by 1 - Otherwise,
incr[i]
remains 1 (the sequence breaks) incr[i]
represents how many consecutive non-decreasing elements exist just after indexi
Step 4: Collect good indices
return [i for i in range(k, n - k) if decr[i] >= k and incr[i] >= k]
- Iterate through all valid indices in range
[k, n-k)
- An index
i
is good if both:decr[i] >= k
: at leastk
elements beforei
are non-increasingincr[i] >= k
: at leastk
elements afteri
are non-decreasing
- The list comprehension automatically returns the indices in increasing order
Time Complexity: O(n)
- we make three passes through the array
Space Complexity: O(n)
- for the two auxiliary arrays decr
and incr
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 the solution with nums = [2,1,1,1,3,4,1]
and k = 2
.
Step 1: Initialize arrays
n = 7
- Valid index range:
[2, 5)
→ indices 2, 3, 4 decr = [1, 1, 1, 1, 1, 1, 1, 1]
(size n+1)incr = [1, 1, 1, 1, 1, 1, 1, 1]
(size n+1)
Step 2: Build decr
array (left to right)
For i = 2
:
- Check if
nums[1] <= nums[0]
→1 <= 2
✓ decr[2] = decr[1] + 1 = 2
For i = 3
:
- Check if
nums[2] <= nums[1]
→1 <= 1
✓ decr[3] = decr[2] + 1 = 3
For i = 4
:
- Check if
nums[3] <= nums[2]
→1 <= 1
✓ decr[4] = decr[3] + 1 = 4
For i = 5
:
- Check if
nums[4] <= nums[3]
→3 <= 1
✗ decr[5] = 1
(sequence breaks)
Result: decr = [1, 1, 2, 3, 4, 1, 1, 1]
Step 3: Build incr
array (right to left)
For i = 4
:
- Check if
nums[5] <= nums[6]
→4 <= 1
✗ incr[4] = 1
(sequence breaks)
For i = 3
:
- Check if
nums[4] <= nums[5]
→3 <= 4
✓ incr[3] = incr[4] + 1 = 2
For i = 2
:
- Check if
nums[3] <= nums[4]
→1 <= 3
✓ incr[2] = incr[3] + 1 = 3
For i = 1
:
- Check if
nums[2] <= nums[3]
→1 <= 1
✓ incr[1] = incr[2] + 1 = 4
For i = 0
:
- Check if
nums[1] <= nums[2]
→1 <= 1
✓ incr[0] = incr[1] + 1 = 5
Result: incr = [5, 4, 3, 2, 1, 1, 1, 1]
Step 4: Find good indices
Check each valid index in range [2, 5)
:
- Index 2:
decr[2] = 2 >= 2
✓ andincr[2] = 3 >= 2
✓ → GOOD - Index 3:
decr[3] = 3 >= 2
✓ andincr[3] = 2 >= 2
✓ → GOOD - Index 4:
decr[4] = 4 >= 2
✓ andincr[4] = 1 >= 2
✗ → NOT GOOD
Final Answer: [2, 3]
The solution correctly identifies indices 2 and 3 as good:
- Index 2: Elements before
[2,1]
are non-increasing, elements after[1,3]
are non-decreasing - Index 3: Elements before
[1,1]
are non-increasing, elements after[3,4]
are non-decreasing
Solution Implementation
1class Solution:
2 def goodIndices(self, nums: List[int], k: int) -> List[int]:
3 n = len(nums)
4
5 # Track length of non-increasing sequence ending before each index
6 decreasing_length = [1] * (n + 1)
7
8 # Track length of non-decreasing sequence starting after each index
9 increasing_length = [1] * (n + 1)
10
11 # Build decreasing_length array
12 # For each index i, count how many consecutive non-increasing elements end at i-1
13 for i in range(2, n - 1):
14 if nums[i - 1] <= nums[i - 2]:
15 decreasing_length[i] = decreasing_length[i - 1] + 1
16
17 # Build increasing_length array
18 # For each index i, count how many consecutive non-decreasing elements start at i+1
19 for i in range(n - 3, -1, -1):
20 if nums[i + 1] <= nums[i + 2]:
21 increasing_length[i] = increasing_length[i + 1] + 1
22
23 # Find all valid indices where:
24 # - At least k elements before form non-increasing sequence
25 # - At least k elements after form non-decreasing sequence
26 return [i for i in range(k, n - k)
27 if decreasing_length[i] >= k and increasing_length[i] >= k]
28
1class Solution {
2 public List<Integer> goodIndices(int[] nums, int k) {
3 int n = nums.length;
4
5 // Track the length of non-increasing sequence ending before index i
6 int[] nonIncreasingLength = new int[n];
7 // Track the length of non-decreasing sequence starting after index i
8 int[] nonDecreasingLength = new int[n];
9
10 // Initialize all positions with length 1 (single element is always sorted)
11 Arrays.fill(nonIncreasingLength, 1);
12 Arrays.fill(nonDecreasingLength, 1);
13
14 // Build non-increasing sequence lengths from left to right
15 // Start from index 2 to ensure we have at least 2 elements to compare
16 for (int i = 2; i < n - 1; i++) {
17 // If previous element is >= the one before it (non-increasing)
18 if (nums[i - 1] <= nums[i - 2]) {
19 nonIncreasingLength[i] = nonIncreasingLength[i - 1] + 1;
20 }
21 }
22
23 // Build non-decreasing sequence lengths from right to left
24 // Start from n-3 to ensure we have at least 2 elements to compare
25 for (int i = n - 3; i >= 0; i--) {
26 // If next element is >= the one after it (non-decreasing)
27 if (nums[i + 1] <= nums[i + 2]) {
28 nonDecreasingLength[i] = nonDecreasingLength[i + 1] + 1;
29 }
30 }
31
32 // Collect all good indices
33 List<Integer> goodIndicesList = new ArrayList<>();
34
35 // Check each valid index (must have k elements before and after)
36 for (int i = k; i < n - k; i++) {
37 // Index i is good if:
38 // - k elements before i form non-increasing sequence
39 // - k elements after i form non-decreasing sequence
40 if (nonIncreasingLength[i] >= k && nonDecreasingLength[i] >= k) {
41 goodIndicesList.add(i);
42 }
43 }
44
45 return goodIndicesList;
46 }
47}
48
1class Solution {
2public:
3 vector<int> goodIndices(vector<int>& nums, int k) {
4 int n = nums.size();
5
6 // Track the length of non-increasing sequence ending just before index i
7 vector<int> nonIncreasingLength(n, 1);
8
9 // Track the length of non-decreasing sequence starting just after index i
10 vector<int> nonDecreasingLength(n, 1);
11
12 // Build the non-increasing length array from left to right
13 // Starting from index 2 since we need to look at i-1 and i-2
14 for (int i = 2; i < n; ++i) {
15 // If nums[i-2] >= nums[i-1], the non-increasing sequence continues
16 if (nums[i - 1] <= nums[i - 2]) {
17 nonIncreasingLength[i] = nonIncreasingLength[i - 1] + 1;
18 }
19 }
20
21 // Build the non-decreasing length array from right to left
22 // Starting from n-3 since we need to look at i+1 and i+2
23 for (int i = n - 3; i >= 0; --i) {
24 // If nums[i+1] <= nums[i+2], the non-decreasing sequence continues
25 if (nums[i + 1] <= nums[i + 2]) {
26 nonDecreasingLength[i] = nonDecreasingLength[i + 1] + 1;
27 }
28 }
29
30 // Collect all good indices
31 vector<int> result;
32
33 // Check each valid index (must have k elements before and after)
34 for (int i = k; i < n - k; ++i) {
35 // Good index if:
36 // - k elements before i form non-increasing sequence
37 // - k elements after i form non-decreasing sequence
38 if (nonIncreasingLength[i] >= k && nonDecreasingLength[i] >= k) {
39 result.push_back(i);
40 }
41 }
42
43 return result;
44 }
45};
46
1function goodIndices(nums: number[], k: number): number[] {
2 const n = nums.length;
3
4 // Track the length of non-increasing sequence ending just before index i
5 const nonIncreasingLength: number[] = new Array(n).fill(1);
6
7 // Track the length of non-decreasing sequence starting just after index i
8 const nonDecreasingLength: number[] = new Array(n).fill(1);
9
10 // Build the non-increasing length array from left to right
11 // Starting from index 2 since we need to look at i-1 and i-2
12 for (let i = 2; i < n; i++) {
13 // If nums[i-2] >= nums[i-1], the non-increasing sequence continues
14 if (nums[i - 1] <= nums[i - 2]) {
15 nonIncreasingLength[i] = nonIncreasingLength[i - 1] + 1;
16 }
17 }
18
19 // Build the non-decreasing length array from right to left
20 // Starting from n-3 since we need to look at i+1 and i+2
21 for (let i = n - 3; i >= 0; i--) {
22 // If nums[i+1] <= nums[i+2], the non-decreasing sequence continues
23 if (nums[i + 1] <= nums[i + 2]) {
24 nonDecreasingLength[i] = nonDecreasingLength[i + 1] + 1;
25 }
26 }
27
28 // Collect all good indices
29 const result: number[] = [];
30
31 // Check each valid index (must have k elements before and after)
32 for (let i = k; i < n - k; i++) {
33 // Good index if:
34 // - k elements before i form non-increasing sequence
35 // - k elements after i form non-decreasing sequence
36 if (nonIncreasingLength[i] >= k && nonDecreasingLength[i] >= k) {
37 result.push(i);
38 }
39 }
40
41 return result;
42}
43
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of three main parts:
- First loop: Iterates from index 2 to
n-2
, performing constant time operations at each step -O(n)
- Second loop: Iterates from index
n-3
to 0, performing constant time operations at each step -O(n)
- List comprehension: Iterates through indices from
k
ton-k
, performing constant time comparisons at each index -O(n)
Since these operations are sequential and not nested, the total time complexity is O(n) + O(n) + O(n) = O(n)
.
Space Complexity: O(n)
The algorithm uses:
decr
array of sizen+1
-O(n)
incr
array of sizen+1
-O(n)
- Output list in the worst case could contain up to
n-2k
elements -O(n)
The total space complexity is O(n) + O(n) + O(n) = O(n)
, where n
is the length of the input array.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Errors in Index Boundaries
Pitfall: One of the most common mistakes is incorrectly setting the loop boundaries when building the decreasing_length
and increasing_length
arrays. Developers often confuse:
- Whether to use
n-1
orn-2
as the upper bound - Whether to check
nums[i-1] <= nums[i-2]
ornums[i] <= nums[i-1]
Why it happens: The relationship between the index i
and the elements being compared can be confusing. For decreasing_length[i]
, we're tracking the sequence that ends just before index i
, not at index i
.
Solution:
# Correct: For decreasing_length[i], we check elements BEFORE index i
for i in range(2, n - 1): # Start at 2 because we need nums[i-2]
if nums[i - 1] <= nums[i - 2]: # Compare the two elements before i
decreasing_length[i] = decreasing_length[i - 1] + 1
# Correct: For increasing_length[i], we check elements AFTER index i
for i in range(n - 3, -1, -1): # Stop at n-3 because we need nums[i+2]
if nums[i + 1] <= nums[i + 2]: # Compare the two elements after i
increasing_length[i] = increasing_length[i + 1] + 1
2. Misunderstanding What the DP Arrays Represent
Pitfall: Confusing what decreasing_length[i]
and increasing_length[i]
actually represent. Some might think:
decreasing_length[i]
counts elements including indexi
increasing_length[i]
counts elements including indexi
Why it happens: The naming and indexing can be misleading without careful documentation.
Solution: Always remember:
decreasing_length[i]
= length of non-increasing sequence ending at positioni-1
(excludingi
)increasing_length[i]
= length of non-decreasing sequence starting at positioni+1
(excludingi
)
Add clear comments:
# decreasing_length[i]: How many consecutive non-increasing elements # exist BEFORE index i (not including i) # increasing_length[i]: How many consecutive non-decreasing elements # exist AFTER index i (not including i)
3. Incorrect Comparison Operators
Pitfall: Using the wrong comparison operator (<
instead of <=
or vice versa) when checking for non-increasing or non-decreasing sequences.
Why it happens: The problem requires:
- Non-increasing: each element ≤ previous (allows equal values)
- Non-decreasing: each element ≥ previous (allows equal values)
Solution: Use the correct operators:
# For non-increasing (allowing equal values): use <= if nums[i - 1] <= nums[i - 2]: # Correct # For non-decreasing (allowing equal values): use <= if nums[i + 1] <= nums[i + 2]: # Correct # Common mistake: using < instead of <= if nums[i - 1] < nums[i - 2]: # Wrong! This checks strictly decreasing
4. Edge Case Handling with Small Arrays
Pitfall: Not properly handling cases where n < 2k + 1
, which means no valid indices can exist.
Why it happens: The range [k, n-k)
can be empty if the array is too small.
Solution: Add an early return check:
def goodIndices(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
# Early return if array is too small to have any good indices
if n < 2 * k + 1:
return []
# Continue with the main algorithm...
5. Array Size Initialization Error
Pitfall: Creating arrays of size n
instead of n+1
, or not understanding why n+1
might be used.
Why it happens: Some implementations use size n+1
for easier indexing or to avoid boundary checks, but this can cause confusion.
Solution: Be consistent with array sizing and indexing:
# Option 1: Use size n (more memory efficient) decreasing_length = [1] * n increasing_length = [1] * n # Option 2: Use size n+1 (might simplify some boundary cases) decreasing_length = [1] * (n + 1) increasing_length = [1] * (n + 1) # But ensure you don't access out-of-bounds indices
The key is to be consistent throughout your implementation and understand how your chosen size affects the indexing logic.
How many times is a tree node visited in a depth first search?
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!