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
andj
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.
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:
-
Expand the window: Iterate through the array with the right pointer
j
and elementx
:- Update the OR value:
s |= x
- Update bit counts: For each bit position
h
from 0 to 31, if bith
is set inx
(checked byx >> h & 1
), incrementcnt[h]
- Update the OR value:
-
Shrink the window when valid: While
s >= k
andi <= 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 iny
, decrementcnt[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
- If bit
- Move the left pointer:
i += 1
- Update the minimum length:
-
Return the result:
- If
ans > n
(meaning it wasn't updated), no valid subarray exists, return-1
- Otherwise, return
ans
- If
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 EvaluatorExample 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
- Bit 1 is set in 2, so
- Move left:
i = 1
- Check:
9 >= 10
? No, exit shrinking - Window:
[1, 8]
, OR = 9
- Update answer:
Final Result:
ans = 3
(not > n), so return3
- 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 needlog₂ M
bits to represent a number up toM
) - The inner while loop with the two pointers technique ensures each element is visited at most twice (once when
j
expands and once wheni
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.
Which of the following array represent a max heap?
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!