Find All Good Indices
MedPrompt
You are given a 0-indexed integer array nums of size n and a positive integer k.
We call an index i in the range k <= i < n - k good if the following conditions are satisfied:
- The
kelements that are just before the indexiare in non-increasing order. - The
kelements that are just after the indexiare in non-decreasing order.
Return an array of all good indices sorted in increasing order.
Example 1:
Input: nums = [2,1,1,1,3,4,1], k = 2
Output: [2,3]
Explanation: There are two good indices in the array:
- Index 2. The subarray [2,1] is in non-increasing order, and the subarray [1,3] is in non-decreasing order.
- Index 3. The subarray [1,1] is in non-increasing order, and the subarray [3,4] is in non-decreasing order.
Note that the index 4 is not good because [4,1] is not non-decreasing.Example 2:
Input: nums = [2,1,1,2], k = 2
Output: []
Explanation: There are no good indices in this array.
Constraints:
n == nums.length3 <= n <= 1051 <= nums[i] <= 1061 <= k <= n / 2
Approaches
2 approaches with complexity analysis and trade-offs.
The most straightforward approach is to iterate through every possible index i that could be a 'good' index and, for each one, verify if it meets the two specified conditions directly by checking the subarrays before and after it.
Algorithm
- Initialize an empty list
resultto store the good indices. - Iterate through each index
iin the range[k, n - k - 1]. - For each
i, we perform two separate checks:- Check Before: Create a loop to check the
kelements beforei. Iterate fromj = i - ktoi - 2. Ifnums[j] < nums[j+1], the condition is not met, so we can stop checking for thisiand continue to the next. - Check After: If the first check passed, create another loop to check the
kelements afteri. Iterate fromj = i + 1toi + k - 1. Ifnums[j] > nums[j+1], the condition is not met.
- Check Before: Create a loop to check the
- If both checks pass, add
ito theresultlist. - After the main loop finishes, return the
resultlist.
Walkthrough
We can loop through all indices i from k to n - k - 1. For each i, we perform two separate checks:
- Check Before: We check if the subarray
nums[i-k...i-1]is non-increasing. This is done by iterating fromj = i-ktoi-2and ensuringnums[j] >= nums[j+1]for allj. - Check After: If the first condition holds, we check if the subarray
nums[i+1...i+k]is non-decreasing. This is done by iterating fromj = i+1toi+k-1and ensuringnums[j] <= nums[j+1]for allj.
If both conditions are satisfied, we add the index i to our result list. Since we iterate through i in increasing order, the final list will also be sorted.
import java.util.ArrayList;import java.util.List; class Solution { public List<Integer> goodIndices(int[] nums, int k) { int n = nums.length; List<Integer> result = new ArrayList<>(); // Iterate through all possible good indices for (int i = k; i < n - k; i++) { // Check the k elements before index i boolean isNonIncreasing = true; for (int j = i - k; j < i - 1; j++) { if (nums[j] < nums[j + 1]) { isNonIncreasing = false; break; } } if (isNonIncreasing) { // Check the k elements after index i boolean isNonDecreasing = true; for (int j = i + 1; j < i + k; j++) { if (nums[j] > nums[j + 1]) { isNonDecreasing = false; break; } } if (isNonDecreasing) { result.add(i); } } } return result; }}Complexity
Time
O(n * k), where n is the number of elements in `nums`. For each of the `n - 2k` potential indices, we perform two separate checks, each taking O(k) time. This can lead to a Time Limit Exceeded error for large inputs.
Space
O(1) if we don't count the output list. The space required for the result list can be up to O(n) in the worst case.
Trade-offs
Pros
Simple to understand and implement.
Cons
Inefficient due to redundant computations of checking subarray properties.
Likely to time out on larger test cases.
A more efficient approach is to avoid the redundant checks of the brute-force method by precomputing the necessary information. We can use dynamic programming to create two arrays: one that stores the length of the non-increasing subarray ending at each index, and another that stores the length of the non-decreasing subarray starting at each index.
Algorithm
- Create an integer array
non_increasingof sizen. - Iterate from
i = 1ton-1. Ifnums[i] <= nums[i-1], setnon_increasing[i] = non_increasing[i-1] + 1. Otherwise, setnon_increasing[i] = 1. Initializenon_increasing[0] = 1. - Create an integer array
non_decreasingof sizen. - Iterate from
i = n-2down to0. Ifnums[i] <= nums[i+1], setnon_decreasing[i] = non_decreasing[i+1] + 1. Otherwise, setnon_decreasing[i] = 1. Initializenon_decreasing[n-1] = 1. - Initialize an empty list
result. - Iterate from
i = kton - k - 1. - Check if
non_increasing[i-1] >= kANDnon_decreasing[i+1] >= k. - If both conditions are true, add
ito theresultlist. - Return the
resultlist.
Walkthrough
The core idea is to solve the problem in three linear passes:
- First Pass (Left to Right): Create an array
non_increasingof sizen.non_increasing[i]will store the length of the contiguous non-increasing subarray ending at indexi. We can compute this by iterating from left to right. Ifnums[i] <= nums[i-1], thennon_increasing[i] = non_increasing[i-1] + 1; otherwise, the streak is broken, andnon_increasing[i] = 1. - Second Pass (Right to Left): Create an array
non_decreasingof sizen.non_decreasing[i]will store the length of the contiguous non-decreasing subarray starting at indexi. We compute this by iterating from right to left. Ifnums[i] <= nums[i+1], thennon_decreasing[i] = non_decreasing[i+1] + 1; otherwise,non_decreasing[i] = 1. - Third Pass (Finding Good Indices): Iterate through the potential good indices
ifromkton - k - 1. An indexiis good if:- The
kelements before it are non-increasing. This is true if the length of the non-increasing subarray ending ati-1is at leastk. We check this withnon_increasing[i-1] >= k. - The
kelements after it are non-decreasing. This is true if the length of the non-decreasing subarray starting ati+1is at leastk. We check this withnon_decreasing[i+1] >= k. If both conditions are met, addito the result list.
- The
import java.util.ArrayList;import java.util.List; class Solution { public List<Integer> goodIndices(int[] nums, int k) { int n = nums.length; // non_increasing[i] stores the length of the non-increasing subarray ending at i int[] non_increasing = new int[n]; non_increasing[0] = 1; for (int i = 1; i < n; i++) { if (nums[i] <= nums[i - 1]) { non_increasing[i] = non_increasing[i - 1] + 1; } else { non_increasing[i] = 1; } } // non_decreasing[i] stores the length of the non-decreasing subarray starting at i int[] non_decreasing = new int[n]; non_decreasing[n - 1] = 1; for (int i = n - 2; i >= 0; i--) { if (nums[i] <= nums[i + 1]) { non_decreasing[i] = non_decreasing[i + 1] + 1; } else { non_decreasing[i] = 1; } } List<Integer> result = new ArrayList<>(); // Iterate through possible good indices for (int i = k; i < n - k; i++) { // Check if the k elements before i are non-increasing boolean beforeOk = non_increasing[i - 1] >= k; // Check if the k elements after i are non-decreasing boolean afterOk = non_decreasing[i + 1] >= k; if (beforeOk && afterOk) { result.add(i); } } return result; }}Complexity
Time
O(n), where n is the number of elements in `nums`. We perform three separate passes over the array: one to compute non-increasing lengths, one for non-decreasing lengths, and one to find the good indices. Each pass takes O(n) time.
Space
O(n) to store the two precomputed arrays (`non_increasing` and `non_decreasing`).
Trade-offs
Pros
Highly efficient with a linear time complexity.
Guaranteed to pass all test cases within the time limit.
Cons
Requires O(n) extra space for the DP arrays, which might be a concern for very large
nin a memory-constrained environment.
Solutions
Solution
class Solution {public List<Integer> goodIndices(int[] nums, int k) { int n = nums.length; int[] decr = new int[n]; int[] incr = new int[n]; Arrays.fill(decr, 1); Arrays.fill(incr, 1); for (int i = 2; i < n - 1; ++i) { if (nums[i - 1] <= nums[i - 2]) { decr[i] = decr[i - 1] + 1; } } for (int i = n - 3; i >= 0; --i) { if (nums[i + 1] <= nums[i + 2]) { incr[i] = incr[i + 1] + 1; } } List<Integer> ans = new ArrayList<>(); for (int i = k; i < n - k; ++i) { if (decr[i] >= k && incr[i] >= k) { ans.add(i); } } return ans; }}class Solution {public: vector<int> goodIndices(vector<int> &nums, int k) { int n = nums.size(); vector<int> decr(n, 1); vector<int> incr(n, 1); for (int i = 2; i < n; ++i) { if (nums[i - 1] <= nums[i - 2]) { decr[i] = decr[i - 1] + 1; } } for (int i = n - 3; ~i; --i) { if (nums[i + 1] <= nums[i + 2]) { incr[i] = incr[i + 1] + 1; } } vector<int> ans; for (int i = k; i < n - k; ++i) { if (decr[i] >= k && incr[i] >= k) { ans.push_back(i); } } return ans; }};class Solution: def goodIndices(self, nums: List[int], k: int) -> List[int]: n = len(nums) decr = [1] * (n + 1) incr = [1] * (n + 1) for i in range(2, n - 1): if nums[i - 1] <= nums[i - 2]: decr[i] = decr[i - 1] + 1 for i in range(n - 3, - 1, - 1): if nums[i + 1] <= nums[i + 2]: incr[i] = incr[i + 1] + 1 return [i for i in range(k, n - k) if decr[i] >= k and incr[i] >= k]Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.