3097. Shortest Subarray With OR at Least K II
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]
andk = 7
:- The bitwise OR is:
3 | 1 | 5 = 7
- Since
7 >= 7
, this subarray is special - The length of this subarray is
3
- The bitwise OR is:
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
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:
-
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. -
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.
-
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]
wherecnt[h]
tells us how many numbers in our window have bith
set to1
. -
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)
.
- When adding a number: For each bit set in the new number, increment its count. The overall OR gets updated by simply doing
-
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
- Expand right pointer: Keep adding elements until
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 windows
: The current bitwise OR value of the windowi, j
: Left and right pointers of the sliding windowans
: Tracks the minimum valid window length found
Algorithm Steps:
-
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)
- Set
-
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
-
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
-
Key bit manipulation operations:
x >> h & 1
: Checks if bith
is set in numberx
s |= x
: Adds all bits fromx
to the OR values
s ^= 1 << h
: Toggles bith
ins
(turns it off when we know it should be 0)1 << h
: Creates a number with only bith
set
-
Return result:
- If
ans > n
, no valid subarray was found, return-1
- Otherwise, return
ans
- If
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 bith
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 EvaluatorExample 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
, updateans = 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
- Bit 1:
- 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:
- The OR value grew as we expanded: 0 → 2 → 3 → 11
- When we found a valid window (OR ≥ 10), we tried to shrink it
- When removing element 2, bit 1 was no longer present in any remaining element, so we turned it off using XOR
- The shrinking stopped when the OR dropped below k
- 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 pointeri
- For each removal, it updates the bit count array:
O(32)
- Recalculates the OR value when needed:
O(1)
- For each removal, it updates the bit count array:
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]
where5 = 101₂
,3 = 011₂
,1 = 001₂
- OR value:
5 | 3 | 1 = 111₂ = 7
- If we remove
5
and try7 & ~5 = 111₂ & 010₂ = 010₂ = 2
- But the actual OR of
[3, 1]
is3 | 1 = 011₂ = 3
, not2
!
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...
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
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!