Facebook Pixel

3095. Shortest Subarray With OR at Least K I

Problem Description

You are given an array nums containing non-negative integers and an integer k.

A subarray is considered special if the bitwise OR of all its elements is at least k.

Your task is to find the length of the shortest special non-empty subarray in nums. If no such special subarray exists, return -1.

For example, if you have an array where multiple subarrays have a bitwise OR value greater than or equal to k, you need to return the length of the shortest one among them. The bitwise OR operation combines all elements in the subarray by setting each bit to 1 if at least one element has that bit set to 1.

The solution uses a sliding window approach with two pointers. The key insight is that when fixing the left endpoint of a subarray, as we extend the right endpoint, the bitwise OR value can only increase or stay the same (never decrease). This property allows us to efficiently find the minimum length using a two-pointer technique.

The algorithm maintains:

  • Two pointers i and j for the window boundaries
  • A variable s tracking the current bitwise OR value of the window
  • An array cnt of size 32 to count how many numbers in the window have each bit set

As we expand the window by moving j right, we update the OR value and bit counts. When the OR value becomes at least k, we try to shrink the window from the left while maintaining the special property, updating the minimum length as we go. The bit counting array helps us efficiently update the OR value when removing elements from the left of the window.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key observation is that the bitwise OR operation has a monotonic property: when we add more elements to a subarray, the OR value either increases or stays the same - it never decreases. This happens because OR sets a bit to 1 if at least one number has that bit set, and once a bit is set to 1, adding more numbers can't turn it back to 0.

This monotonic property immediately suggests a sliding window approach. If we have a window [i, j] whose OR value is at least k, we know that any larger window containing it will also have an OR value at least k. So we can try to shrink the window to find the minimum length.

The challenge is efficiently updating the OR value when we remove elements from the left of the window. Simply recalculating the OR of all remaining elements would be inefficient.

Here's where bit counting comes in. We maintain a count array cnt[32] where cnt[h] tells us how many numbers in our current window have the h-th bit set. When we add a number to the window, we increment the count for each of its set bits. When we remove a number, we decrement the counts.

The crucial insight: a bit position contributes to the OR value if and only if at least one number in the window has that bit set (i.e., cnt[h] > 0). So when removing a number from the window:

  • If after decrementing, cnt[h] becomes 0, that bit is no longer set in the OR value
  • We can update the OR value by turning off that specific bit using XOR: s ^= (1 << h)

This allows us to maintain the OR value in O(32) time per operation rather than recalculating it from scratch, making the sliding window approach efficient.

Learn more about Sliding Window patterns.

Solution Approach

The solution uses a sliding window technique with two pointers combined with bit counting to efficiently track the bitwise OR value.

Initialization:

  • Set n = len(nums) to store the array length
  • Initialize cnt = [0] * 32 to track the count of set bits at each position (0 to 31)
  • Set ans = n + 1 as the initial minimum length (impossible value to ensure any valid length will be smaller)
  • Initialize s = 0 to track the current OR value of the window
  • Set i = 0 as the left pointer of the window

Main Algorithm:

  1. Expand the window: Iterate through the array with the right pointer j and element x:

    • Update the OR value: s |= x
    • Update bit counts: For each bit position h from 0 to 31, if bit h is set in x (checked by x >> h & 1), increment cnt[h]
  2. Shrink the window when valid: While s >= k and i <= j:

    • Update the minimum length: ans = min(ans, j - i + 1)
    • Prepare to remove the leftmost element y = nums[i]
    • Update bit counts for removal: For each bit position h:
      • If bit h is set in y, decrement cnt[h]
      • If cnt[h] becomes 0, that bit is no longer present in any element of the window
      • Remove that bit from the OR value: s ^= 1 << h
    • Move the left pointer: i += 1
  3. Return the result:

    • If ans > n (meaning it wasn't updated), no valid subarray exists, return -1
    • Otherwise, return ans

Time Complexity: O(32n) = O(n), where we process each element at most twice (once when adding, once when removing) and perform O(32) bit operations for each.

Space Complexity: O(32) = O(1) for the bit count array.

The elegance of this solution lies in how it maintains the OR value efficiently. Instead of recalculating the entire OR when the window shrinks, it uses the bit count array to know exactly which bits to turn off, achieving constant time updates per element.

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 the algorithm with nums = [2, 1, 8] and k = 10.

Initial Setup:

  • n = 3, ans = 4 (n + 1), s = 0, i = 0
  • cnt = [0] * 32 (all zeros)

Step 1: j = 0, x = 2 (binary: 010)

  • Expand window: s = 0 | 2 = 2
  • Update bit counts: bit 1 is set, so cnt[1] = 1
  • Check if s >= k: 2 >= 10? No, continue
  • Window: [2], OR = 2

Step 2: j = 1, x = 1 (binary: 001)

  • Expand window: s = 2 | 1 = 3
  • Update bit counts: bit 0 is set, so cnt[0] = 1
  • Check if s >= k: 3 >= 10? No, continue
  • Window: [2, 1], OR = 3

Step 3: j = 2, x = 8 (binary: 1000)

  • Expand window: s = 3 | 8 = 11
  • Update bit counts: bit 3 is set, so cnt[3] = 1
  • Check if s >= k: 11 >= 10? Yes! Enter shrinking phase

Shrinking Phase:

While s >= k and i <= j:

  • i = 0:
    • Update answer: ans = min(4, 2 - 0 + 1) = 3
    • Remove nums[0] = 2 (binary: 010):
      • Bit 1 is set in 2, so cnt[1] = 1 - 1 = 0
      • Since cnt[1] became 0, remove bit 1: s = 11 ^ 2 = 9
    • Move left: i = 1
    • Check: 9 >= 10? No, exit shrinking
    • Window: [1, 8], OR = 9

Final Result:

  • ans = 3 (not > n), so return 3
  • The shortest special subarray is [2, 1, 8] with length 3

Note: The algorithm found that we need all three elements to achieve an OR value ≥ 10. When we tried to shrink by removing the first element (2), the OR value dropped below k (from 11 to 9), confirming that the minimum length is indeed 3.

Solution Implementation

1class Solution:
2    def minimumSubarrayLength(self, nums: List[int], k: int) -> int:
3        # Total number of elements in the array
4        n = len(nums)
5      
6        # Track count of set bits at each position (0-31) in current window
7        # bit_count[i] represents how many numbers in window have bit i set
8        bit_count = [0] * 32
9      
10        # Initialize minimum length to impossible value (n+1)
11        min_length = n + 1
12      
13        # Current OR value of the window
14        current_or = 0
15      
16        # Left pointer of sliding window
17        left = 0
18      
19        # Iterate through array with right pointer
20        for right, current_num in enumerate(nums):
21            # Add current number to the OR
22            current_or |= current_num
23          
24            # Update bit counts for the new number
25            for bit_pos in range(32):
26                if (current_num >> bit_pos) & 1:
27                    bit_count[bit_pos] += 1
28          
29            # Try to shrink window from left while maintaining OR >= k
30            while current_or >= k and left <= right:
31                # Update minimum length found so far
32                min_length = min(min_length, right - left + 1)
33              
34                # Number we're removing from left side of window
35                removing_num = nums[left]
36              
37                # Update bit counts and OR value after removing left element
38                for bit_pos in range(32):
39                    if (removing_num >> bit_pos) & 1:
40                        bit_count[bit_pos] -= 1
41                        # If no numbers in window have this bit set anymore
42                        if bit_count[bit_pos] == 0:
43                            # Remove this bit from the OR value
44                            current_or ^= (1 << bit_pos)
45              
46                # Move left pointer forward
47                left += 1
48      
49        # Return -1 if no valid subarray found, otherwise return minimum length
50        return -1 if min_length > n else min_length
51
1class Solution {
2    public int minimumSubarrayLength(int[] nums, int k) {
3        int n = nums.length;
4        // Track count of set bits at each position (0-31) in current window
5        int[] bitCount = new int[32];
6        int minLength = n + 1;  // Initialize to impossible value
7      
8        // Sliding window approach with two pointers
9        int left = 0;
10        int currentOR = 0;  // Bitwise OR of current window
11      
12        for (int right = 0; right < n; right++) {
13            // Expand window by including nums[right]
14            currentOR |= nums[right];
15          
16            // Update bit count for nums[right]
17            for (int bitPos = 0; bitPos < 32; bitPos++) {
18                if ((nums[right] >> bitPos & 1) == 1) {
19                    bitCount[bitPos]++;
20                }
21            }
22          
23            // Shrink window from left while OR value is still >= k
24            while (currentOR >= k && left <= right) {
25                // Update minimum length found so far
26                minLength = Math.min(minLength, right - left + 1);
27              
28                // Remove nums[left] from window
29                for (int bitPos = 0; bitPos < 32; bitPos++) {
30                    if ((nums[left] >> bitPos & 1) == 1) {
31                        bitCount[bitPos]--;
32                        // If no elements in window have this bit set anymore,
33                        // remove it from currentOR
34                        if (bitCount[bitPos] == 0) {
35                            currentOR ^= (1 << bitPos);
36                        }
37                    }
38                }
39                left++;
40            }
41        }
42      
43        // Return -1 if no valid subarray found, otherwise return minimum length
44        return minLength > n ? -1 : minLength;
45    }
46}
47
1class Solution {
2public:
3    int minimumSubarrayLength(vector<int>& nums, int k) {
4        int n = nums.size();
5        // Track count of set bits at each position across current window
6        int bitCount[32] = {0};
7      
8        int minLength = n + 1;  // Initialize to impossible value for comparison
9        int left = 0;           // Left pointer of sliding window
10        int currentOR = 0;      // Bitwise OR of current window
11      
12        // Expand window with right pointer
13        for (int right = 0; right < n; ++right) {
14            // Add nums[right] to the window
15            currentOR |= nums[right];
16          
17            // Update bit counts for the new element
18            for (int bitPos = 0; bitPos < 32; ++bitPos) {
19                if ((nums[right] >> bitPos) & 1) {
20                    ++bitCount[bitPos];
21                }
22            }
23          
24            // Contract window while condition is satisfied
25            while (currentOR >= k && left <= right) {
26                // Update minimum length found so far
27                minLength = min(minLength, right - left + 1);
28              
29                // Remove nums[left] from the window
30                for (int bitPos = 0; bitPos < 32; ++bitPos) {
31                    if ((nums[left] >> bitPos) & 1) {
32                        --bitCount[bitPos];
33                        // If no elements have this bit set, remove it from OR
34                        if (bitCount[bitPos] == 0) {
35                            currentOR ^= (1 << bitPos);
36                        }
37                    }
38                }
39                ++left;  // Move left pointer forward
40            }
41        }
42      
43        // Return -1 if no valid subarray found, otherwise return minimum length
44        return minLength > n ? -1 : minLength;
45    }
46};
47
1/**
2 * Finds the minimum length of a subarray whose bitwise OR is at least k
3 * @param nums - The input array of numbers
4 * @param k - The target value for bitwise OR
5 * @returns The minimum subarray length, or -1 if no valid subarray exists
6 */
7function minimumSubarrayLength(nums: number[], k: number): number {
8    const arrayLength: number = nums.length;
9    let minLength: number = arrayLength + 1;
10  
11    // Array to track count of set bits at each position (0-31)
12    const bitCount: number[] = new Array<number>(32).fill(0);
13  
14    // Sliding window approach with two pointers
15    let left: number = 0;
16    let currentOR: number = 0;
17  
18    for (let right: number = 0; right < arrayLength; right++) {
19        // Add current element to the OR result
20        currentOR |= nums[right];
21      
22        // Update bit count for the new element
23        for (let bitPosition: number = 0; bitPosition < 32; bitPosition++) {
24            if (((nums[right] >> bitPosition) & 1) === 1) {
25                bitCount[bitPosition]++;
26            }
27        }
28      
29        // Try to shrink the window from left while maintaining OR >= k
30        while (currentOR >= k && left <= right) {
31            // Update minimum length
32            minLength = Math.min(minLength, right - left + 1);
33          
34            // Remove the leftmost element from the window
35            for (let bitPosition: number = 0; bitPosition < 32; bitPosition++) {
36                if (((nums[left] >> bitPosition) & 1) === 1) {
37                    bitCount[bitPosition]--;
38                    // If no elements in window have this bit set, remove it from OR
39                    if (bitCount[bitPosition] === 0) {
40                        currentOR ^= (1 << bitPosition);
41                    }
42                }
43            }
44            left++;
45        }
46    }
47  
48    // Return -1 if no valid subarray found, otherwise return minimum length
49    return minLength === arrayLength + 1 ? -1 : minLength;
50}
51

Time and Space Complexity

The time complexity is O(n × log M) where n is the length of the array and M is the maximum value of the elements in the array. This is because:

  • The outer loop iterates through all n elements once
  • For each element, we perform bit operations on up to 32 bits (which is log M since we need log₂ M bits to represent a number up to M)
  • The inner while loop with the two pointers technique ensures each element is visited at most twice (once when j expands and once when i contracts)
  • Each iteration of the while loop also involves bit operations on 32 bits

Therefore, the overall time complexity is O(n × 32) = O(n × log M).

The space complexity is O(log M) because:

  • We use a fixed-size array cnt of length 32 to track the count of set bits at each position
  • The value 32 represents the maximum number of bits needed, which is proportional to log₂ M
  • All other variables (n, ans, s, i, j, x, y, h) use constant space

Thus, the space complexity is O(32) = O(log M).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect OR Value Update When Shrinking Window

The Pitfall: A common mistake is trying to recalculate the OR value by simply removing the leftmost element using XOR or subtraction operations, like current_or ^= nums[left]. This is incorrect because bitwise OR is not reversible - you cannot simply "subtract" an element from an OR result.

Why it happens: If multiple elements in the window have the same bit set, removing one element shouldn't turn off that bit in the OR value. For example, if the window is [5, 3] (binary: [101, 011]), the OR is 111. Removing 5 should leave OR as 011, but doing 111 ^ 101 = 010 gives the wrong result.

The Solution: Use a bit counting array to track how many elements have each bit set. Only turn off a bit in the OR value when its count reaches zero:

for bit_pos in range(32):
    if (removing_num >> bit_pos) & 1:
        bit_count[bit_pos] -= 1
        if bit_count[bit_pos] == 0:  # Only turn off bit when count is 0
            current_or ^= (1 << bit_pos)

2. Forgetting to Initialize Minimum Length to an Impossible Value

The Pitfall: Initializing min_length = 0 or min_length = float('inf') instead of min_length = n + 1.

Why it happens: Using 0 would cause the function to always return 0 if no valid subarray exists. Using float('inf') works but is less clean when checking if no solution was found.

The Solution: Initialize to n + 1 (one more than the maximum possible valid length), then check if min_length > n to determine if no valid subarray was found.

3. Off-by-One Error in Window Length Calculation

The Pitfall: Calculating the window length as right - left instead of right - left + 1.

Why it happens: It's easy to forget that when using 0-indexed arrays, the length of a subarray from index i to j inclusive is j - i + 1, not j - i.

The Solution: Always use right - left + 1 when calculating the current window length:

min_length = min(min_length, right - left + 1)

4. Not Handling the Case Where a Single Element Satisfies the Condition

The Pitfall: The sliding window might skip over single-element subarrays if not careful with the loop structure.

Why it happens: If the implementation doesn't check the condition immediately after adding each element, it might miss cases where a single element has an OR value >= k.

The Solution: The code correctly handles this by checking the condition immediately after adding each element to the window and before trying to shrink it. The while loop for shrinking ensures we find the minimum length, including length 1 if applicable.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following array represent a max heap?


Recommended Readings

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

Load More