2653. Sliding Subarray Beauty
Problem Description
You are given an integer array nums
containing n
integers, and you need to find the "beauty" of each subarray of size k
.
The beauty of a subarray is defined as:
- The
x
-th smallest integer in the subarray if that integer is negative 0
if there are fewer thanx
negative integers in the subarray
Your task is to return an integer array containing n - k + 1
integers, where each integer represents the beauty of the corresponding subarray of size k
, in the order they appear when sliding through the original array from left to right.
For example, if you have an array nums
and parameters k
and x
:
- You would look at the first
k
elements, find all negative numbers, and determine if there's anx
-th smallest negative number - Then slide the window one position to the right and repeat
- Continue until you've checked all possible subarrays of size
k
Key points to understand:
- A subarray is a contiguous sequence of elements from the array
- Only negative integers count toward finding the
x
-th smallest - If a subarray doesn't have at least
x
negative integers, its beauty is0
- The result array will have
n - k + 1
elements (one beauty value for each possible subarray of sizek
)
Intuition
The key insight is recognizing that we need to efficiently find the x
-th smallest negative number in each sliding window of size k
. Since the problem involves repeatedly finding order statistics (the x
-th smallest element) as we slide through the array, we need a data structure that can quickly update and query.
The critical observation is that the array elements are bounded within [-50, 50]
. This small range suggests we can use counting sort principles rather than comparison-based sorting. Instead of sorting each window from scratch, we can maintain a frequency count of all elements in the current window.
Why does this work well? Consider:
- With only 101 possible values (
-50
to50
), we can use an arraycnt
of size 101 to track frequencies - To handle negative indices, we shift all values by adding
50
(so-50
becomes index0
,-49
becomes index1
, etc.) - When the window slides, we only need to:
- Increment the count for the new element entering the window
- Decrement the count for the element leaving the window
- This is
O(1)
for updates
To find the x
-th smallest negative number:
- We iterate through the first half of our
cnt
array (indices0
to49
, representing values-50
to-1
) - We accumulate counts until we reach
x
negative numbers - The index where we stop (converted back to the original value) gives us our answer
- If we can't find
x
negative numbers, we return0
This approach transforms what could be an O(k log k)
sorting operation for each window into an O(50)
counting operation, which is effectively O(1)
since 50 is a constant. The sliding window technique ensures we only update counts incrementally rather than recounting everything for each subarray.
Learn more about Sliding Window patterns.
Solution Approach
The solution uses a sliding window technique combined with counting sort to efficiently find the beauty of each subarray.
Data Structure Setup:
- Create a counting array
cnt
of size 101 to track frequencies of elements in the range[-50, 50]
- Add 50 to each element to map them to indices
[0, 100]
(avoiding negative indices)
Helper Function f(x)
:
This function finds the x
-th smallest negative number in the current window:
def f(x: int) -> int:
s = 0
for i in range(50): # Only check indices 0-49 (representing -50 to -1)
s += cnt[i]
if s >= x:
return i - 50 # Convert back to original value
return 0 # If fewer than x negative numbers exist
- Iterate through indices
0
to49
(representing negative values-50
to-1
) - Accumulate the count
s
of negative numbers seen so far - When
s >= x
, we've found thex
-th smallest negative number - Return
i - 50
to convert the index back to the actual negative value - Return
0
if we can't findx
negative numbers
Main Algorithm:
-
Initialize the first window:
cnt = [0] * 101 for v in nums[:k]: cnt[v + 50] += 1
- Fill the counting array with the first
k
elements - Add 50 to each value to get the correct index
- Fill the counting array with the first
-
Calculate beauty for the first window:
ans = [f(x)]
-
Slide the window and update:
for i in range(k, len(nums)): cnt[nums[i] + 50] += 1 # Add new element cnt[nums[i - k] + 50] -= 1 # Remove old element ans.append(f(x)) # Calculate beauty
- For each new position, add the incoming element to
cnt
- Remove the outgoing element (the one that's now
k
positions behind) - Calculate and store the beauty value for the new window
- For each new position, add the incoming element to
Time Complexity: O(n * 50)
where n
is the length of the array. Since 50 is constant, this is effectively O(n)
.
Space Complexity: O(1)
for the counting array (fixed size 101) plus O(n - k + 1)
for the result array.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given:
nums = [1, -1, -3, -2, 3]
k = 3
(window size)x = 2
(find 2nd smallest negative)
Step 1: Initialize counting array and first window
Create cnt
array of size 101 (all zeros initially). Process first window [1, -1, -3]
:
1 + 50 = 51
:cnt[51] = 1
-1 + 50 = 49
:cnt[49] = 1
-3 + 50 = 47
:cnt[47] = 1
Step 2: Find beauty of first window [1, -1, -3]
Call f(2)
to find 2nd smallest negative:
- Check indices 0-49 (representing -50 to -1)
- At index 47:
s = 0 + 1 = 1
(found 1 negative: -3) - At index 49:
s = 1 + 1 = 2
(found 2 negatives: -3, -1) - Since
s >= 2
, return49 - 50 = -1
Beauty of first window = -1
Step 3: Slide to second window [-1, -3, -2]
Update counts:
- Remove
nums[0] = 1
:cnt[51] -= 1
→cnt[51] = 0
- Add
nums[3] = -2
:cnt[48] += 1
→cnt[48] = 1
Current cnt
has:
cnt[47] = 1
(representing -3)cnt[48] = 1
(representing -2)cnt[49] = 1
(representing -1)
Call f(2)
:
- At index 47:
s = 1
(found -3) - At index 48:
s = 2
(found -3, -2) - Since
s >= 2
, return48 - 50 = -2
Beauty of second window = -2
Step 4: Slide to third window [-3, -2, 3]
Update counts:
- Remove
nums[1] = -1
:cnt[49] -= 1
→cnt[49] = 0
- Add
nums[4] = 3
:cnt[53] += 1
→cnt[53] = 1
Current cnt
has:
cnt[47] = 1
(representing -3)cnt[48] = 1
(representing -2)
Call f(2)
:
- At index 47:
s = 1
(found -3) - At index 48:
s = 2
(found -3, -2) - Since
s >= 2
, return48 - 50 = -2
Beauty of third window = -2
Final Result: [-1, -2, -2]
The key insight is that we never sort the entire window. Instead, we maintain counts and traverse them in order when needed, making updates O(1) and queries O(50) = O(1).
Solution Implementation
1class Solution:
2 def getSubarrayBeauty(self, nums: List[int], k: int, x: int) -> List[int]:
3 def find_xth_smallest_negative(x: int) -> int:
4 """
5 Find the x-th smallest value in the current window.
6 Returns the value if it's negative, otherwise returns 0.
7 """
8 cumulative_count = 0
9 # Iterate through negative numbers only (indices 0-49 represent -50 to -1)
10 for i in range(50):
11 cumulative_count += frequency_count[i]
12 if cumulative_count >= x:
13 # Convert index back to actual value (i - 50)
14 return i - 50
15 # If x-th smallest is not negative, return 0
16 return 0
17
18 # Initialize frequency array for values from -50 to 50
19 # Index mapping: value v maps to index (v + 50)
20 # e.g., -50 -> index 0, 0 -> index 50, 50 -> index 100
21 frequency_count = [0] * 101
22
23 # Build initial window of size k
24 for value in nums[:k]:
25 frequency_count[value + 50] += 1
26
27 # Calculate beauty for the first window
28 result = [find_xth_smallest_negative(x)]
29
30 # Sliding window approach for remaining elements
31 for i in range(k, len(nums)):
32 # Add new element to the window
33 frequency_count[nums[i] + 50] += 1
34 # Remove the leftmost element from the window
35 frequency_count[nums[i - k] + 50] -= 1
36 # Calculate beauty for current window
37 result.append(find_xth_smallest_negative(x))
38
39 return result
40
1class Solution {
2 public int[] getSubarrayBeauty(int[] nums, int k, int x) {
3 int n = nums.length;
4
5 // Frequency array to count occurrences of each number
6 // Index mapping: actual value + 50 = index (to handle negative numbers)
7 // Range: [-50, 50] maps to [0, 100]
8 int[] frequencyCount = new int[101];
9
10 // Initialize frequency count for the first window of size k
11 for (int i = 0; i < k; i++) {
12 frequencyCount[nums[i] + 50]++;
13 }
14
15 // Result array to store beauty values for each subarray
16 int[] result = new int[n - k + 1];
17
18 // Calculate beauty for the first window
19 result[0] = findXthSmallestNegative(frequencyCount, x);
20
21 // Sliding window approach: process remaining subarrays
22 int resultIndex = 1;
23 for (int windowEnd = k; windowEnd < n; windowEnd++) {
24 // Add the new element entering the window
25 frequencyCount[nums[windowEnd] + 50]++;
26
27 // Remove the element leaving the window
28 frequencyCount[nums[windowEnd - k] + 50]--;
29
30 // Calculate beauty for current window and store in result
31 result[resultIndex++] = findXthSmallestNegative(frequencyCount, x);
32 }
33
34 return result;
35 }
36
37 /**
38 * Finds the x-th smallest negative number in the frequency array.
39 * Returns 0 if there are fewer than x negative numbers.
40 *
41 * @param frequencyCount frequency array with counts of each number
42 * @param x the position of the smallest negative number to find
43 * @return the x-th smallest negative number, or 0 if not enough negatives
44 */
45 private int findXthSmallestNegative(int[] frequencyCount, int x) {
46 int cumulativeSum = 0;
47
48 // Iterate through negative numbers only (indices 0-49 represent -50 to -1)
49 for (int i = 0; i < 50; i++) {
50 cumulativeSum += frequencyCount[i];
51
52 // If cumulative sum reaches x, we found the x-th smallest negative
53 if (cumulativeSum >= x) {
54 // Convert index back to actual value
55 return i - 50;
56 }
57 }
58
59 // If we haven't found x negative numbers, return 0
60 return 0;
61 }
62}
63
1class Solution {
2public:
3 vector<int> getSubarrayBeauty(vector<int>& nums, int k, int x) {
4 int n = nums.size();
5 // Frequency array for values in range [-50, 50]
6 // Index mapping: value v maps to index (v + 50)
7 int frequency[101] = {};
8
9 // Initialize frequency count for the first window of size k
10 for (int i = 0; i < k; ++i) {
11 ++frequency[nums[i] + 50];
12 }
13
14 // Result array to store beauty values for each subarray
15 vector<int> result(n - k + 1);
16
17 // Lambda function to find the x-th smallest element in current window
18 // Returns the x-th smallest negative number, or 0 if there are fewer than x negative numbers
19 auto findXthSmallest = [&](int x) {
20 int cumulativeCount = 0;
21 // Only check negative values (indices 0 to 49 represent -50 to -1)
22 for (int i = 0; i < 50; ++i) {
23 cumulativeCount += frequency[i];
24 if (cumulativeCount >= x) {
25 // Convert index back to actual value
26 return i - 50;
27 }
28 }
29 // If we don't have x negative numbers, return 0
30 return 0;
31 };
32
33 // Calculate beauty for the first subarray
34 result[0] = findXthSmallest(x);
35
36 // Sliding window: process remaining subarrays
37 for (int windowEnd = k, resultIndex = 1; windowEnd < n; ++windowEnd) {
38 // Add new element to the window
39 ++frequency[nums[windowEnd] + 50];
40 // Remove element that's sliding out of the window
41 --frequency[nums[windowEnd - k] + 50];
42 // Calculate and store beauty for current window
43 result[resultIndex++] = findXthSmallest(x);
44 }
45
46 return result;
47 }
48};
49
1/**
2 * Finds the beauty of each subarray of size k in the given array.
3 * Beauty is defined as the x-th smallest negative number in the subarray, or 0 if there are fewer than x negative numbers.
4 *
5 * @param nums - The input array of integers
6 * @param k - The size of each subarray
7 * @param x - The position of the smallest negative number to find (1-indexed)
8 * @returns An array containing the beauty of each subarray
9 */
10function getSubarrayBeauty(nums: number[], k: number, x: number): number[] {
11 const arrayLength: number = nums.length;
12
13 // Frequency array to count occurrences of numbers in range [-50, 50]
14 // Index mapping: actual value + 50 = index (e.g., -50 maps to index 0, 0 maps to index 50)
15 const frequencyCount: number[] = new Array(101).fill(0);
16
17 // Initialize frequency count for the first window of size k
18 for (let i = 0; i < k; ++i) {
19 ++frequencyCount[nums[i] + 50];
20 }
21
22 // Result array to store beauty values for each subarray
23 const beautyResults: number[] = new Array(arrayLength - k + 1);
24
25 /**
26 * Helper function to find the x-th smallest negative number in current window
27 *
28 * @param targetPosition - The position of the smallest negative number to find
29 * @returns The x-th smallest negative number, or 0 if not enough negative numbers
30 */
31 const findXthSmallestNegative = (targetPosition: number): number => {
32 let cumulativeSum = 0;
33
34 // Iterate through negative numbers only (indices 0-49 represent values -50 to -1)
35 for (let i = 0; i < 50; ++i) {
36 cumulativeSum += frequencyCount[i];
37
38 // If cumulative sum reaches target position, we found the x-th smallest
39 if (cumulativeSum >= targetPosition) {
40 return i - 50; // Convert index back to actual value
41 }
42 }
43
44 // Not enough negative numbers, return 0
45 return 0;
46 };
47
48 // Calculate beauty for the first subarray
49 beautyResults[0] = findXthSmallestNegative(x);
50
51 // Use sliding window technique to process remaining subarrays
52 for (let windowEnd = k, resultIndex = 1; windowEnd < arrayLength; ++windowEnd, ++resultIndex) {
53 // Add new element to the window
54 frequencyCount[nums[windowEnd] + 50]++;
55
56 // Remove element that's sliding out of the window
57 frequencyCount[nums[windowEnd - k] + 50]--;
58
59 // Calculate beauty for current subarray
60 beautyResults[resultIndex] = findXthSmallestNegative(x);
61 }
62
63 return beautyResults;
64}
65
Time and Space Complexity
The time complexity is O(n × 50)
, where n
is the length of the array nums
. This is because:
- The sliding window iterates through the array once, taking
O(n)
time - For each window position, the function
f(x)
is called, which iterates through at most 50 elements (indices 0 to 49 of thecnt
array, representing negative numbers from -50 to -1) - Therefore, the total time complexity is
O(n × 50)
The space complexity is O(100)
or simply O(1)
since it's constant. This is because:
- The
cnt
array has a fixed size of 101 elements to store the frequency count of numbers in the range[-50, 50]
- The
ans
array storesn - k + 1
results, but this is considered as output space and typically not counted in space complexity analysis - All other variables use constant space
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Index Mapping for Negative Numbers
One of the most common mistakes is incorrectly mapping array values to counting array indices, especially when dealing with negative numbers.
Pitfall Example:
# WRONG: Forgetting to offset negative values frequency_count[nums[i]] += 1 # This will cause IndexError for negative values! # WRONG: Using wrong offset frequency_count[nums[i] + 51] += 1 # Off-by-one error in offset
Solution: Always use consistent offset of 50 to map the range [-50, 50] to [0, 100]:
frequency_count[value + 50] += 1 # Correct mapping
2. Iterating Through Wrong Range When Finding x-th Smallest
Another critical mistake is iterating through all 101 indices instead of just the negative value range.
Pitfall Example:
def find_xth_smallest_negative(x: int) -> int:
cumulative_count = 0
# WRONG: Iterating through all values including positives
for i in range(101):
cumulative_count += frequency_count[i]
if cumulative_count >= x:
return i - 50 # This might return a positive number!
return 0
Solution: Only iterate through indices 0-49 (representing -50 to -1):
def find_xth_smallest_negative(x: int) -> int:
cumulative_count = 0
for i in range(50): # Only negative values
cumulative_count += frequency_count[i]
if cumulative_count >= x:
return i - 50
return 0
3. Sliding Window Update Order Error
Updating the window in the wrong order or using incorrect indices can lead to wrong results.
Pitfall Example:
# WRONG: Removing wrong element (off-by-one error)
for i in range(k, len(nums)):
frequency_count[nums[i] + 50] += 1
frequency_count[nums[i - k - 1] + 50] -= 1 # Wrong! Should be i - k
# WRONG: Calculating beauty before updating window
for i in range(k, len(nums)):
result.append(find_xth_smallest_negative(x)) # Calculate before update
frequency_count[nums[i] + 50] += 1
frequency_count[nums[i - k] + 50] -= 1
Solution: Always update the window first (add new, remove old), then calculate beauty:
for i in range(k, len(nums)):
frequency_count[nums[i] + 50] += 1 # Add new element
frequency_count[nums[i - k] + 50] -= 1 # Remove element k positions back
result.append(find_xth_smallest_negative(x)) # Then calculate beauty
4. Misunderstanding the Beauty Definition
Confusing when to return 0 vs the actual value.
Pitfall Example:
def find_xth_smallest_negative(x: int) -> int:
cumulative_count = 0
for i in range(50):
cumulative_count += frequency_count[i]
if cumulative_count >= x:
# WRONG: Always returning the value even if it's not negative
return i - 50
# WRONG: Returning the x-th smallest regardless of sign
for i in range(50, 101): # Checking positive numbers
cumulative_count += frequency_count[i]
if cumulative_count >= x:
return i - 50 # This would return a positive number!
return 0
Solution: The beauty is specifically the x-th smallest negative integer, or 0 if there aren't enough negative integers:
- Only consider negative values (indices 0-49)
- Return 0 if we can't find x negative numbers
- Never return positive values as beauty
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
Recommended Readings
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
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!