2488. Count Subarrays With Median K
Problem Description
You are given an array nums
of size n
containing distinct integers from 1
to n
, and a positive integer k
.
Your task is to find the number of non-empty subarrays in nums
that have a median equal to k
.
Key definitions:
- Median: The middle element of an array after sorting it in ascending order. If the array has an even number of elements, the median is the left middle element (the smaller of the two middle elements).
- For example:
- The median of
[2,3,1,4]
is2
(after sorting:[1,2,3,4]
, the left middle element is2
) - The median of
[8,4,3,5,1]
is4
(after sorting:[1,3,4,5,8]
, the middle element is4
)
- The median of
- For example:
- Subarray: A contiguous portion of the original array
The problem asks you to count how many subarrays exist where, if you were to sort that subarray, the median would be exactly k
.
For instance, if nums = [3,2,1,4,5]
and k = 4
, you need to find all contiguous subarrays where 4
would be the median after sorting that subarray.
Intuition
Since the array contains distinct integers from 1
to n
, and we're looking for subarrays with median k
, we can leverage a key observation: for k
to be the median of a subarray, the number of elements greater than k
and the number of elements less than k
must be balanced in a specific way.
For an odd-length subarray, k
is the median when there are equal numbers of elements greater than and less than k
. For an even-length subarray, k
is the median when there's exactly one more element less than k
than elements greater than k
.
This leads us to think about the problem differently: instead of checking every possible subarray and computing its median, we can track the balance between elements greater than k
and elements less than k
.
Let's define a balance score: for each element, if it's greater than k
, we add +1
; if it's less than k
, we add -1
. For a subarray containing k
:
- If the balance is
0
, we have an odd-length subarray withk
as median - If the balance is
-1
, we have an even-length subarray withk
as median
Since every valid subarray must contain k
itself, we can start from k
's position and expand outward. We traverse to the right of k
, keeping track of the balance scores we encounter. Then, when we traverse to the left of k
, we look for complementary balances on the right side that would make the total balance either 0
or -1
.
For example, if we have a balance of +2
on the left side of k
, we need a balance of -2
(for odd-length) or -1
(for even-length) on the right side to create a valid subarray with median k
.
This approach transforms the problem from checking all subarrays to efficiently counting valid combinations using the balance scores, reducing our time complexity significantly.
Learn more about Prefix Sum patterns.
Solution Approach
The implementation follows the intuition by tracking balance scores as we traverse from k
's position in both directions.
Step 1: Find the position of k
i = nums.index(k)
First, we locate where k
appears in the array, storing its index as i
.
Step 2: Initialize variables
ans = 1
: Start with 1 because the single element[k]
itself is a valid subarray with mediank
cnt = Counter()
: A dictionary to store balance scores from the right sidex = 0
: Tracks the running balance score
Step 3: Traverse right from k
for v in nums[i + 1 :]: x += 1 if v > k else -1 ans += 0 <= x <= 1 cnt[x] += 1
Starting from position i + 1
, we traverse to the right:
- For each element
v
, update balancex
: add+1
ifv > k
, otherwise-1
- If
x
is0
or1
, the subarray fromk
to current position has mediank
, so incrementans
- Store the balance
x
in countercnt
for later use when combining with left side
Step 4: Traverse left from k
x = 0
for j in range(i - 1, -1, -1):
x += 1 if nums[j] > k else -1
ans += 0 <= x <= 1
ans += cnt[-x] + cnt[-x + 1]
Reset x = 0
and traverse leftward from position i - 1
:
- Update balance
x
the same way:+1
if element >k
, otherwise-1
- If
x
is0
or1
, the subarray from current position tok
has mediank
, so incrementans
- Check for complementary balances on the right side:
cnt[-x]
: Creates subarrays with total balance0
(odd-length with mediank
)cnt[-x + 1]
: Creates subarrays with total balance1
(even-length with mediank
)- Add both counts to
ans
Why this works:
When we have a left balance of x
and a right balance of y
, the total balance of the subarray spanning both sides is x + y
. For k
to be the median:
- Total balance should be
0
(odd-length) → needy = -x
- Total balance should be
1
(even-length) → needy = -x + 1
The algorithm efficiently counts all valid subarrays in O(n)
time using O(n)
space for the counter.
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, 5, 1, 4, 3]
and k = 4
.
Step 1: Find position of k
k = 4
is at indexi = 3
Step 2: Traverse right from k (index 4)
- Start with
x = 0
,ans = 1
(counting[4]
itself) - Process element
3
at index 4:- Since
3 < 4
, add-1
:x = -1
- Check if
0 ≤ x ≤ 1
: No, so don't incrementans
- Store in counter:
cnt[-1] = 1
- Since
Right side balances collected: cnt = {-1: 1}
Step 3: Traverse left from k (indices 2, 1, 0)
- Reset
x = 0
Process index 2 (element 1
):
- Since
1 < 4
, add-1
:x = -1
- Check if
0 ≤ x ≤ 1
: No - Look for complementary balances:
cnt[-(-1)] = cnt[1]
= 0 (not found)cnt[-(-1) + 1] = cnt[2]
= 0 (not found)
ans
remains 1
Process index 1 (element 5
):
- Since
5 > 4
, add+1
:x = -1 + 1 = 0
- Check if
0 ≤ x ≤ 1
: Yes! Incrementans
to 2- This counts subarray
[5, 1, 4]
with balance 0
- This counts subarray
- Look for complementary balances:
cnt[-0] = cnt[0]
= 0 (not found)cnt[-0 + 1] = cnt[1]
= 0 (not found)
ans
remains 2
Process index 0 (element 2
):
- Since
2 < 4
, add-1
:x = 0 - 1 = -1
- Check if
0 ≤ x ≤ 1
: No - Look for complementary balances:
cnt[-(-1)] = cnt[1]
= 0 (not found)cnt[-(-1) + 1] = cnt[2]
= 0 (not found)
ans
remains 2
Valid subarrays found:
[4]
- single element, median is 4[5, 1, 4]
- sorted:[1, 4, 5]
, median is 4
Final answer: 2
Solution Implementation
1class Solution:
2 def countSubarrays(self, nums: List[int], k: int) -> int:
3 # Find the index of element k in the array
4 k_index = nums.index(k)
5
6 # Dictionary to store balance counts from the right side of k
7 right_balance_count = Counter()
8
9 # Initialize result with 1 (subarray containing only k itself)
10 result = 1
11
12 # Calculate balance for elements to the right of k
13 # Balance: +1 for elements > k, -1 for elements < k
14 balance = 0
15 for value in nums[k_index + 1:]:
16 if value > k:
17 balance += 1
18 else:
19 balance -= 1
20
21 # Count subarrays starting at k and ending at current position
22 # Valid if balance is 0 (equal elements on both sides) or 1 (one more larger element)
23 if 0 <= balance <= 1:
24 result += 1
25
26 # Store the balance count for later use with left side
27 right_balance_count[balance] += 1
28
29 # Calculate balance for elements to the left of k
30 balance = 0
31 for j in range(k_index - 1, -1, -1):
32 if nums[j] > k:
33 balance += 1
34 else:
35 balance -= 1
36
37 # Count subarrays starting at current position and ending at k
38 # Valid if balance is 0 or 1
39 if 0 <= balance <= 1:
40 result += 1
41
42 # Count subarrays that span both sides of k
43 # We need the total balance to be 0 or 1
44 # So we look for right balances that complement the left balance
45 result += right_balance_count[-balance] + right_balance_count[-balance + 1]
46
47 return result
48
1class Solution {
2 public int countSubarrays(int[] nums, int k) {
3 int n = nums.length;
4
5 // Find the index of element k in the array
6 int kIndex = 0;
7 while (nums[kIndex] != k) {
8 kIndex++;
9 }
10
11 // Create a frequency array to store balance counts
12 // Size is (2*n + 1) to handle negative indices after offset
13 int[] balanceCount = new int[(n << 1) | 1];
14
15 // Initialize answer with 1 (subarray containing only k itself)
16 int answer = 1;
17
18 // Process elements to the right of k
19 int balance = 0;
20 for (int j = kIndex + 1; j < n; j++) {
21 // Increment balance if element > k, decrement if element < k
22 balance += nums[j] > k ? 1 : -1;
23
24 // Check if current subarray from kIndex to j has valid median
25 // Balance of 0 means equal numbers greater and less than k
26 // Balance of 1 means one more element greater than k (odd length with k as median)
27 if (balance >= 0 && balance <= 1) {
28 answer++;
29 }
30
31 // Store the balance count with offset n to handle negative indices
32 balanceCount[balance + n]++;
33 }
34
35 // Process elements to the left of k
36 balance = 0;
37 for (int j = kIndex - 1; j >= 0; j--) {
38 // Increment balance if element > k, decrement if element < k
39 balance += nums[j] > k ? 1 : -1;
40
41 // Check if subarray from j to kIndex has valid median
42 if (balance >= 0 && balance <= 1) {
43 answer++;
44 }
45
46 // Add subarrays that span both sides of k
47 // For a valid median, left balance + right balance should be 0 or 1
48 // So we look for right balances of -leftBalance or -leftBalance + 1
49 answer += balanceCount[-balance + n] + balanceCount[-balance + 1 + n];
50 }
51
52 return answer;
53 }
54}
55
1class Solution {
2public:
3 int countSubarrays(vector<int>& nums, int k) {
4 int n = nums.size();
5
6 // Find the index of element k in the array
7 int pivotIndex = find(nums.begin(), nums.end(), k) - nums.begin();
8
9 // Create a frequency array to store balance counts
10 // Size is (2*n + 1) to handle negative indices after offset
11 vector<int> balanceCount(2 * n + 1, 0);
12
13 // Initialize answer with 1 (the single element k itself forms a valid subarray)
14 int answer = 1;
15
16 // Process elements to the right of k
17 int balance = 0;
18 for (int j = pivotIndex + 1; j < n; ++j) {
19 // Increment balance if current element > k, decrement if < k
20 balance += (nums[j] > k) ? 1 : -1;
21
22 // Check if current subarray [pivotIndex, j] has median k
23 // Balance of 0 means equal elements greater and less than k
24 // Balance of 1 means one more element greater than k (valid for odd-length subarrays)
25 if (balance >= 0 && balance <= 1) {
26 ++answer;
27 }
28
29 // Store the balance count with offset n to handle negative indices
30 ++balanceCount[balance + n];
31 }
32
33 // Process elements to the left of k
34 balance = 0;
35 for (int j = pivotIndex - 1; j >= 0; --j) {
36 // Increment balance if current element > k, decrement if < k
37 balance += (nums[j] > k) ? 1 : -1;
38
39 // Check if current subarray [j, pivotIndex] has median k
40 if (balance >= 0 && balance <= 1) {
41 ++answer;
42 }
43
44 // Add subarrays that extend both left and right of k
45 // We need the total balance to be 0 or 1 for valid median
46 // So we look for complementary balances on the right side
47 answer += balanceCount[-balance + n] + balanceCount[-balance + 1 + n];
48 }
49
50 return answer;
51 }
52};
53
1/**
2 * Counts the number of subarrays where k is the median
3 * @param nums - The input array of distinct integers
4 * @param k - The target value that should be the median
5 * @returns The count of subarrays where k is the median
6 */
7function countSubarrays(nums: number[], k: number): number {
8 // Find the index of k in the array
9 const indexOfK: number = nums.indexOf(k);
10 const arrayLength: number = nums.length;
11
12 // Create a count array to track balance values
13 // Size is (2 * n + 1) to handle negative indices after offset
14 const balanceCount: number[] = new Array((arrayLength << 1) | 1).fill(0);
15
16 // Initialize answer with 1 (subarray containing only k itself)
17 let answer: number = 1;
18
19 // Process elements to the right of k
20 let balance: number = 0;
21 for (let j: number = indexOfK + 1; j < arrayLength; ++j) {
22 // Increment balance if element > k, decrement if element < k
23 balance += nums[j] > k ? 1 : -1;
24
25 // If balance is 0 or 1, k is the median for subarray from indexOfK to j
26 answer += (balance >= 0 && balance <= 1) ? 1 : 0;
27
28 // Store the balance count for later use (offset by arrayLength to handle negative indices)
29 ++balanceCount[balance + arrayLength];
30 }
31
32 // Process elements to the left of k
33 balance = 0;
34 for (let j: number = indexOfK - 1; j >= 0; --j) {
35 // Increment balance if element > k, decrement if element < k
36 balance += nums[j] > k ? 1 : -1;
37
38 // If balance is 0 or 1, k is the median for subarray from j to indexOfK
39 answer += (balance >= 0 && balance <= 1) ? 1 : 0;
40
41 // Add subarrays that span both sides of k
42 // We need the opposite balance on the right side to make k the median
43 answer += balanceCount[-balance + arrayLength] + balanceCount[-balance + 1 + arrayLength];
44 }
45
46 return answer;
47}
48
Time and Space Complexity
Time Complexity: O(n)
The algorithm performs the following operations:
- Finding the index of
k
usingnums.index(k)
:O(n)
in the worst case - First loop iterating through elements from index
i+1
to the end:O(n-i-1)
operations - Second loop iterating from index
i-1
down to 0:O(i)
operations - Within each loop iteration, all operations (arithmetic, dictionary access/update, counter increments) are
O(1)
Total time complexity: O(n) + O(n-i-1) + O(i) = O(n)
Space Complexity: O(n)
The algorithm uses:
cnt
Counter/dictionary that stores at mostn
different values ofx
(in the worst case, each element after indexi
produces a unique cumulative sum)- A few integer variables (
i
,ans
,x
):O(1)
The Counter cnt
can have at most O(n)
entries since the value of x
changes by ±1 at each step and we process at most n
elements, leading to a space complexity of O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrectly Handling the Balance Calculation
A common mistake is confusing when to add +1 or -1 to the balance. Some might incorrectly write:
# WRONG: This reverses the logic balance += 1 if value < k else -1
This error completely breaks the algorithm because the balance no longer correctly represents the difference between elements greater than k and elements less than k.
Solution: Remember that we want to track how many MORE elements are greater than k compared to those less than k. Always use +1
for elements greater than k and -1
for elements less than k.
2. Forgetting to Initialize Result with 1
Some implementations might start with result = 0
, forgetting that the single-element subarray [k]
itself is valid:
# WRONG: Missing the single element [k] case result = 0
Solution: Always initialize with result = 1
since a subarray containing only k has k as its median by definition.
3. Using Wrong Complement Values When Combining Left and Right
A critical error is using the wrong complement when checking subarrays that span both sides:
# WRONG: Using positive values instead of negative result += right_balance_count[balance] + right_balance_count[balance + 1]
This fails because if the left side has balance x
, we need the right side to have balance -x
or -x + 1
to achieve a total balance of 0 or 1.
Solution: Always use negative complements: right_balance_count[-balance]
and right_balance_count[-balance + 1]
.
4. Not Handling the Case Where k Doesn't Exist in the Array
While the problem states k exists in the array, defensive programming might add error handling:
# WRONG: Will throw ValueError if k not in nums k_index = nums.index(k)
Solution: For production code, consider adding validation:
if k not in nums: return 0 k_index = nums.index(k)
5. Confusing the Order of Operations
Some might try to combine both traversals in a single loop or process left side before right side:
# WRONG: Processing left side first won't have right balance counts ready
balance = 0
for j in range(k_index - 1, -1, -1):
# ... trying to use right_balance_count which is empty
Solution: Always process the right side first to populate the right_balance_count
dictionary, then process the left side to combine with the stored right balances.
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
Recommended Readings
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!