Facebook Pixel

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 than x 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 an x-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 is 0
  • The result array will have n - k + 1 elements (one beauty value for each possible subarray of size k)
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. With only 101 possible values (-50 to 50), we can use an array cnt of size 101 to track frequencies
  2. To handle negative indices, we shift all values by adding 50 (so -50 becomes index 0, -49 becomes index 1, etc.)
  3. 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 (indices 0 to 49, 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 return 0

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 to 49 (representing negative values -50 to -1)
  • Accumulate the count s of negative numbers seen so far
  • When s >= x, we've found the x-th smallest negative number
  • Return i - 50 to convert the index back to the actual negative value
  • Return 0 if we can't find x negative numbers

Main Algorithm:

  1. 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
  2. Calculate beauty for the first window:

    ans = [f(x)]
  3. 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

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 Evaluator

Example 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, return 49 - 50 = -1

Beauty of first window = -1

Step 3: Slide to second window [-1, -3, -2]

Update counts:

  • Remove nums[0] = 1: cnt[51] -= 1cnt[51] = 0
  • Add nums[3] = -2: cnt[48] += 1cnt[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, return 48 - 50 = -2

Beauty of second window = -2

Step 4: Slide to third window [-3, -2, 3]

Update counts:

  • Remove nums[1] = -1: cnt[49] -= 1cnt[49] = 0
  • Add nums[4] = 3: cnt[53] += 1cnt[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, return 48 - 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 the cnt 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 stores n - 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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?


Recommended Readings

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

Load More