Facebook Pixel

3097. Shortest Subarray With OR at Least K II

MediumBit ManipulationArraySliding Window
Leetcode Link

Problem Description

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

An array is considered special when 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 within nums. If no such special subarray exists, return -1.

What is bitwise OR? The bitwise OR operation combines bits from numbers. For any bit position, if at least one number has a 1 at that position, the result will have a 1 at that position.

Example understanding:

  • If you have a subarray [3, 1, 5] and k = 7:
    • The bitwise OR is: 3 | 1 | 5 = 7
    • Since 7 >= 7, this subarray is special
    • The length of this subarray is 3

Key points:

  • You need to check all possible subarrays
  • A subarray must be contiguous (consecutive elements from the original array)
  • The subarray must be non-empty (contain at least one element)
  • Among all special subarrays, return the length of the shortest one
  • If no subarray produces a bitwise OR value of at least k, return -1
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: as we add more elements to a subarray, the OR value either stays the same or increases - it never decreases. This happens because OR can only "turn on" bits (change 0 to 1), never "turn off" bits.

This monotonic property suggests we can use a sliding window approach with two pointers. Here's the thinking process:

  1. Why sliding window works: If we fix the left endpoint and extend the right endpoint, the OR value grows. Once we find a valid window where OR >= k, we can try shrinking from the left to find the minimum length.

  2. The challenge with OR: Unlike sum operations, we can't simply subtract when shrinking the window. When we remove an element from the left, we need to recalculate the OR of the remaining elements. But recalculating from scratch each time would be inefficient.

  3. The bit-counting trick: Instead of recalculating, we can track how many times each bit position appears in our current window. We maintain a count array cnt[32] where cnt[h] tells us how many numbers in our window have bit h set to 1.

  4. Maintaining the OR value efficiently:

    • When adding a number: For each bit set in the new number, increment its count. The overall OR gets updated by simply doing s |= new_number.
    • When removing a number: For each bit set in the removed number, decrement its count. If any bit's count drops to 0, that bit should be turned off in our OR value using XOR: s ^= (1 << h).
  5. The two-pointer movement:

    • Expand right pointer: Keep adding elements until OR >= k
    • Once valid: Try shrinking from left while maintaining OR >= k to find minimum length
    • Track the minimum valid window size throughout

This approach efficiently finds the shortest special subarray in O(n × 32) time, where each element is visited at most twice (once by each pointer), and for each element, we check at most 32 bits.

Learn more about Sliding Window patterns.

Solution Approach

We implement a two-pointer sliding window approach with bit counting to efficiently track the bitwise OR of the current window.

Data Structures:

  • cnt[32]: An array to count occurrences of each bit position (0-31) in the current window
  • s: The current bitwise OR value of the window
  • i, j: Left and right pointers of the sliding window
  • ans: Tracks the minimum valid window length found

Algorithm Steps:

  1. Initialize variables:

    • Set ans = n + 1 (larger than any possible valid answer)
    • Set s = 0 (empty OR value)
    • Set i = 0 (left pointer at start)
    • Create cnt = [0] * 32 (bit counter array)
  2. Expand window with right pointer j:

    for j, x in enumerate(nums):
        s |= x  # Update OR value
        # Count bits in x
        for h in range(32):
            if x >> h & 1:  # Check if bit h is set
                cnt[h] += 1
  3. Shrink window when valid (s >= k):

    while s >= k and i <= j:
        ans = min(ans, j - i + 1)  # Update minimum length
        y = nums[i]  # Element to remove
      
        # Update bit counts and OR value
        for h in range(32):
            if y >> h & 1:  # If bit h is set in y
                cnt[h] -= 1
                if cnt[h] == 0:  # No more elements have this bit
                    s ^= 1 << h  # Turn off bit h in s
        i += 1  # Move left pointer
  4. Key bit manipulation operations:

    • x >> h & 1: Checks if bit h is set in number x
    • s |= x: Adds all bits from x to the OR value s
    • s ^= 1 << h: Toggles bit h in s (turns it off when we know it should be 0)
    • 1 << h: Creates a number with only bit h set
  5. Return result:

    • If ans > n, no valid subarray was found, return -1
    • Otherwise, return ans

Why this works:

  • When expanding the window, OR can only increase or stay the same, so we simply do s |= x
  • When shrinking, we need to track which bits are still present. If removing an element causes a bit's count to reach 0, that bit should no longer be in the OR result
  • The XOR operation s ^= 1 << h effectively turns off bit h when its count becomes 0

Time Complexity: O(n × 32) = O(n) where n is the array length. Each element is processed at most twice (once by each pointer), and for each element, we check 32 bits.

Space Complexity: O(32) = O(1) for the bit counter 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 trace through the algorithm with nums = [2, 1, 8] and k = 10.

Initial Setup:

  • ans = 4 (n + 1)
  • s = 0 (current OR value)
  • i = 0 (left pointer)
  • cnt = [0] * 32 (bit counter)

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

  • Expand: s = 0 | 2 = 2
  • Update bit counts: bit 1 is set, so cnt[1] = 1
  • Check: s = 2 < k = 10, continue expanding
  • Window: [2], OR = 2

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

  • Expand: s = 2 | 1 = 3
  • Update bit counts: bit 0 is set, so cnt[0] = 1
  • Check: s = 3 < k = 10, continue expanding
  • Window: [2, 1], OR = 3

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

  • Expand: s = 3 | 8 = 11
  • Update bit counts: bit 3 is set, so cnt[3] = 1
  • Check: s = 11 >= k = 10, valid window found!
  • Window: [2, 1, 8], OR = 11

Shrinking phase (while s >= k):

Shrink 1: Remove nums[0] = 2

  • Current window length: 2 - 0 + 1 = 3, update ans = 3
  • Remove element 2 (binary: 010)
    • Bit 1: cnt[1] = 1 - 1 = 0 (no more elements have bit 1)
    • Since cnt[1] = 0, turn off bit 1: s = 11 ^ 2 = 9
  • Move left pointer: i = 1
  • Check: s = 9 < k = 10, stop shrinking
  • New window: [1, 8], OR = 9

Final Result:

  • ans = 3 (the minimum valid window was [2, 1, 8])
  • Return 3

Key Insights from this Example:

  1. The OR value grew as we expanded: 0 → 2 → 3 → 11
  2. When we found a valid window (OR ≥ 10), we tried to shrink it
  3. When removing element 2, bit 1 was no longer present in any remaining element, so we turned it off using XOR
  4. The shrinking stopped when the OR dropped below k
  5. The shortest special subarray has length 3

Solution Implementation

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

Time Complexity: O(n * 32) = O(n)

The algorithm uses a sliding window approach with two pointers i and j. The outer loop iterates through all elements with pointer j from 0 to n-1. For each element, it performs:

  • Bitwise OR operation: O(1)
  • Updating bit count array by checking 32 bits: O(32)
  • The inner while loop moves pointer i forward, and each element is visited at most once by pointer i
    • For each removal, it updates the bit count array: O(32)
    • Recalculates the OR value when needed: O(1)

Since each element is visited at most twice (once by j and once by i), and for each visit we perform O(32) operations, the total time complexity is O(n * 32) = O(n).

Space Complexity: O(32) = O(1)

The algorithm uses:

  • A fixed-size array cnt of length 32 to track the count of set bits at each position: O(32)
  • Several integer variables (n, ans, s, i, j, x, y, h): O(1)

Since the space used doesn't depend on the input size and 32 is a constant, the space complexity is O(1).

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

Common Pitfalls

Pitfall 1: Incorrect OR Value Update When Shrinking Window

The Problem: A common mistake is trying to recalculate the OR value by simply removing bits from the leaving element:

# WRONG approach:
current_or &= ~left_num  # This doesn't work!

Why it fails: The bitwise OR of a window isn't simply the OR of individual elements. When an element leaves the window, its bits might still be present in other elements. For example:

  • Window: [5, 3, 1] where 5 = 101₂, 3 = 011₂, 1 = 001₂
  • OR value: 5 | 3 | 1 = 111₂ = 7
  • If we remove 5 and try 7 & ~5 = 111₂ & 010₂ = 010₂ = 2
  • But the actual OR of [3, 1] is 3 | 1 = 011₂ = 3, not 2!

The Solution: Track the count of each bit position across all elements in the window. Only turn off a bit when its count reaches zero:

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

Pitfall 2: Early Termination When First Valid Window is Found

The Problem:

# WRONG: Returning immediately when finding first valid window
for right in range(n):
    current_or |= nums[right]
    if current_or >= k:
        return right + 1  # This might not be the shortest!

Why it fails: The first valid window found might not be the shortest. Consider:

  • Array: [1, 2, 8], k = 7
  • First valid window: [1, 2, 8] with OR = 11₂ = 11 (length 3)
  • But [8] alone has OR = 8 which is >= 7 (length 1)

The Solution: Continue searching even after finding valid windows, using the sliding window technique to explore all possibilities:

while current_or >= k and left <= right:
    min_length = min(min_length, right - left + 1)
    # Try to shrink further...
    left += 1

Pitfall 3: Forgetting to Handle Single Element Cases

The Problem: Not checking if individual elements already satisfy the condition:

# Missing optimization that could cause inefficiency
for num in nums:
    if num >= k:
        return 1  # Immediate answer!

Why it matters: While the sliding window approach will eventually find single-element solutions, checking upfront can provide early termination and better performance.

The Solution: The current implementation handles this correctly as the window naturally shrinks to size 1 when possible, but adding an early check can optimize:

# Optional optimization at the start
for num in nums:
    if num >= k:
        return 1

# Continue with sliding window for other cases...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?


Recommended Readings

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

Load More