2411. Smallest Subarrays With Maximum Bitwise OR
Problem Description
You are given a 0-indexed array nums
of length n
containing non-negative integers. For each starting position i
(from 0 to n-1), you need to find the length of the shortest subarray that begins at position i
and achieves the maximum possible bitwise OR value among all subarrays starting at i
.
Let's break down what this means:
- For any position
i
, consider all possible subarrays that start at indexi
:nums[i...i]
,nums[i...i+1]
,nums[i...i+2]
, ...,nums[i...n-1]
- Calculate the bitwise OR for each of these subarrays
- Find the maximum bitwise OR value among all these subarrays
- Determine the shortest subarray starting at
i
that achieves this maximum OR value
The bitwise OR operation combines bits from numbers where the result bit is 1 if at least one of the corresponding bits in the input numbers is 1.
Your task is to return an array answer
of length n
where answer[i]
represents the length of the minimum-sized subarray starting at index i
that has the maximum possible bitwise OR value.
For example, if starting from index i
, the maximum OR is achieved by the subarray nums[i...i+2]
, then answer[i] = 3
(since the subarray contains 3 elements).
A subarray is defined as a contiguous sequence of elements from the original array, and it must be non-empty.
Intuition
The key insight is understanding how the bitwise OR operation works: once a bit becomes 1 in the OR result, it stays 1 as we include more elements. This means the OR value can only increase or stay the same as we extend a subarray - it never decreases.
For any starting position i
, the maximum possible OR value is achieved by taking the OR of all elements from i
to the end of the array. However, we might reach this maximum value earlier with a shorter subarray.
To find the shortest subarray that achieves the maximum OR, we need to determine which bits contribute to the final result. A bit position contributes to the maximum OR if there exists at least one element in the range [i, n-1]
that has that bit set to 1.
Working backwards from the end of the array gives us an advantage: we can track the most recent (rightmost) position where each bit was set to 1. This is crucial because:
- When we're at position
i
, we already know where each bit appears next in the array (from our reverse traversal) - The minimum subarray length needed is determined by the farthest position we need to reach to collect all the necessary 1 bits
The algorithm uses an array f
of size 32 (for 32-bit integers) where f[j]
stores the most recent index where bit j
was set to 1. As we move backwards:
- If the current number has bit
j
set, we updatef[j]
to the current position - If the current number doesn't have bit
j
set butf[j]
indicates there's a 1 for this bit position ahead, we need to extend our subarray to at least positionf[j]
to include that bit
The minimum subarray length starting at position i
is therefore the maximum distance we need to travel to collect all the bits that appear anywhere from position i
onwards.
Learn more about Binary Search and Sliding Window patterns.
Solution Approach
The solution implements a reverse traversal approach with bit manipulation to efficiently find the minimum subarray length for each starting position.
Data Structure:
ans
: An array of sizen
initialized with all 1s, storing the final answer for each positionf
: An array of size 32 initialized with -1, tracking the most recent position where each bit is set to 1
Algorithm Steps:
-
Initialize the tracking arrays:
ans = [1] * n
- Each position has at least a subarray of length 1 (the element itself)f = [-1] * 32
- No bits have been seen yet (using -1 as sentinel value)
-
Traverse the array in reverse from index
n-1
to0
:For each position
i
:- Initialize
t = 1
to track the minimum subarray length needed
- Initialize
-
Check each bit position (0 to 31):
for j in range(32): if (nums[i] >> j) & 1: # Check if bit j is set in nums[i] f[j] = i # Update the position of this bit elif f[j] != -1: # Bit j is not set, but exists ahead t = max(t, f[j] - i + 1) # Update minimum length needed
- If bit
j
is set innums[i]
: Updatef[j] = i
to mark this as the new rightmost position for bitj
- If bit
j
is not set butf[j] != -1
: This bit exists somewhere to the right, so we need to extend our subarray to include it. The length needed isf[j] - i + 1
- If bit
-
Update the answer:
ans[i] = t
stores the minimum subarray length for positioni
Why this works:
- By traversing backwards, when we reach position
i
, arrayf
contains the positions of all bits that appear innums[i+1]
throughnums[n-1]
- The maximum OR starting from position
i
must include all bits that appear anywhere fromi
onwards - The minimum subarray length is determined by the farthest bit we need to include
- Since OR operation is monotonic (values can only increase or stay same), once we include all necessary bits, we've achieved the maximum OR
Time Complexity: O(32n) = O(n)
where n is the length of the array
Space Complexity: O(32) = O(1)
for the bit tracking array (not counting the output array)
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a small example: nums = [5, 2, 7]
First, let's understand what we're looking for:
- For index 0: subarrays are [5], [5,2], [5,2,7] with ORs of 5, 7, 7 respectively. Maximum OR is 7, achieved first by [5,2], so answer[0] = 2
- For index 1: subarrays are [2], [2,7] with ORs of 2, 7 respectively. Maximum OR is 7, achieved by [2,7], so answer[1] = 2
- For index 2: only subarray is [7] with OR of 7. So answer[2] = 1
Now let's trace through the algorithm:
Initial Setup:
ans = [1, 1, 1]
(every position starts with minimum length 1)f = [-1] * 32
(no bits seen yet)- Binary representations: 5 = 101, 2 = 010, 7 = 111
Iteration 1: i = 2, nums[2] = 7 (binary: 111)
t = 1
(minimum length tracker)- Check bits 0-31:
- Bit 0: 7 has bit 0 set →
f[0] = 2
- Bit 1: 7 has bit 1 set →
f[1] = 2
- Bit 2: 7 has bit 2 set →
f[2] = 2
- Bits 3-31: not set in 7, and
f[j] = -1
, so skip
- Bit 0: 7 has bit 0 set →
ans[2] = t = 1
- State:
f = [2, 2, 2, -1, -1, ...]
Iteration 2: i = 1, nums[1] = 2 (binary: 010)
t = 1
- Check bits:
- Bit 0: 2 doesn't have bit 0, but
f[0] = 2
(bit 0 exists at position 2) → Need to extend:t = max(1, 2-1+1) = 2
- Bit 1: 2 has bit 1 set →
f[1] = 1
- Bit 2: 2 doesn't have bit 2, but
f[2] = 2
(bit 2 exists at position 2) → Need to extend:t = max(2, 2-1+1) = 2
- Bit 0: 2 doesn't have bit 0, but
ans[1] = t = 2
(need to reach position 2 to get all bits)- State:
f = [2, 1, 2, -1, -1, ...]
Iteration 3: i = 0, nums[0] = 5 (binary: 101)
t = 1
- Check bits:
- Bit 0: 5 has bit 0 set →
f[0] = 0
- Bit 1: 5 doesn't have bit 1, but
f[1] = 1
(bit 1 exists at position 1) → Need to extend:t = max(1, 1-0+1) = 2
- Bit 2: 5 has bit 2 set →
f[2] = 0
- Bit 0: 5 has bit 0 set →
ans[0] = t = 2
(need to reach position 1 to include bit 1)
Final Result: ans = [2, 2, 1]
The key insight: When processing position 0, we discovered we need bit 1 (which only exists at position 1 or later), so the minimum subarray must extend to at least position 1, giving us length 2. This matches our expected answer!
Solution Implementation
1class Solution:
2 def smallestSubarrays(self, nums: List[int]) -> List[int]:
3 """
4 For each position i, find the smallest subarray starting at i
5 that has the maximum possible bitwise OR value.
6
7 Args:
8 nums: List of non-negative integers
9
10 Returns:
11 List where ans[i] is the length of smallest subarray starting at i
12 with maximum OR value
13 """
14 n = len(nums)
15 # Initialize result array - minimum subarray length is 1 (single element)
16 result = [1] * n
17
18 # Track the rightmost position where each bit (0-31) appears as 1
19 # -1 means the bit hasn't been seen yet
20 rightmost_bit_position = [-1] * 32
21
22 # Process array from right to left
23 for current_index in range(n - 1, -1, -1):
24 # Minimum subarray length starting at current position
25 min_length = 1
26
27 # Check each bit position (0 to 31)
28 for bit_position in range(32):
29 # Check if current number has this bit set
30 if (nums[current_index] >> bit_position) & 1:
31 # Update rightmost position for this bit
32 rightmost_bit_position[bit_position] = current_index
33 elif rightmost_bit_position[bit_position] != -1:
34 # If this bit exists to the right, we need to include it
35 # to achieve maximum OR value
36 distance_to_bit = rightmost_bit_position[bit_position] - current_index + 1
37 min_length = max(min_length, distance_to_bit)
38
39 # Store the minimum length for current starting position
40 result[current_index] = min_length
41
42 return result
43
1class Solution {
2 public int[] smallestSubarrays(int[] nums) {
3 int n = nums.length;
4 int[] result = new int[n];
5
6 // Track the rightmost position where each bit (0-31) is set to 1
7 // lastPositionWithBitSet[j] = index of the last element where bit j is 1
8 int[] lastPositionWithBitSet = new int[32];
9 Arrays.fill(lastPositionWithBitSet, -1);
10
11 // Process array from right to left
12 for (int currentIndex = n - 1; currentIndex >= 0; --currentIndex) {
13 // Minimum subarray length is 1 (the element itself)
14 int minSubarrayLength = 1;
15
16 // Check each bit position (0 to 31 for 32-bit integers)
17 for (int bitPosition = 0; bitPosition < 32; ++bitPosition) {
18 // Check if current bit is set in nums[currentIndex]
19 if (((nums[currentIndex] >> bitPosition) & 1) == 1) {
20 // Update the last position where this bit is set
21 lastPositionWithBitSet[bitPosition] = currentIndex;
22 } else if (lastPositionWithBitSet[bitPosition] != -1) {
23 // If current element doesn't have this bit set, but we know
24 // a position to the right that has it, we need to include
25 // that position in our subarray to maximize OR
26 int requiredLength = lastPositionWithBitSet[bitPosition] - currentIndex + 1;
27 minSubarrayLength = Math.max(minSubarrayLength, requiredLength);
28 }
29 }
30
31 // Store the minimum subarray length starting at currentIndex
32 result[currentIndex] = minSubarrayLength;
33 }
34
35 return result;
36 }
37}
38
1class Solution {
2public:
3 vector<int> smallestSubarrays(vector<int>& nums) {
4 int n = nums.size();
5
6 // Track the rightmost position where each bit (0-31) is set to 1
7 vector<int> rightmostBitPosition(32, -1);
8
9 // Result array to store the smallest subarray length starting from each index
10 vector<int> result(n);
11
12 // Process array from right to left
13 for (int i = n - 1; i >= 0; --i) {
14 // Minimum subarray length is at least 1 (the element itself)
15 int minSubarrayLength = 1;
16
17 // Check each bit position (0 to 31)
18 for (int bitPos = 0; bitPos < 32; ++bitPos) {
19 // Check if current bit is set in nums[i]
20 if ((nums[i] >> bitPos) & 1) {
21 // Update rightmost position for this bit
22 rightmostBitPosition[bitPos] = i;
23 }
24 // If bit is not set but exists somewhere to the right
25 else if (rightmostBitPosition[bitPos] != -1) {
26 // Calculate distance to include this bit in the subarray
27 int distanceToIncludeBit = rightmostBitPosition[bitPos] - i + 1;
28 minSubarrayLength = max(minSubarrayLength, distanceToIncludeBit);
29 }
30 }
31
32 // Store the minimum subarray length for position i
33 result[i] = minSubarrayLength;
34 }
35
36 return result;
37 }
38};
39
1/**
2 * Finds the smallest subarray starting at each position with maximum bitwise OR
3 * @param nums - Input array of numbers
4 * @returns Array where each element represents the length of smallest subarray starting at that index
5 */
6function smallestSubarrays(nums: number[]): number[] {
7 const arrayLength: number = nums.length;
8 const result: number[] = Array(arrayLength).fill(1);
9
10 // Track the most recent position where each bit (0-31) was set to 1
11 const lastBitPosition: number[] = Array(32).fill(-1);
12
13 // Process array from right to left
14 for (let currentIndex = arrayLength - 1; currentIndex >= 0; currentIndex--) {
15 let minSubarrayLength: number = 1;
16
17 // Check each bit position (0 to 31)
18 for (let bitIndex = 0; bitIndex < 32; bitIndex++) {
19 // Check if current bit is set in the current number
20 if ((nums[currentIndex] >> bitIndex) & 1) {
21 // Update the last position where this bit was found
22 lastBitPosition[bitIndex] = currentIndex;
23 } else if (lastBitPosition[bitIndex] !== -1) {
24 // If bit is not set but was found later in array,
25 // we need to extend subarray to include that position
26 minSubarrayLength = Math.max(
27 minSubarrayLength,
28 lastBitPosition[bitIndex] - currentIndex + 1
29 );
30 }
31 }
32
33 result[currentIndex] = minSubarrayLength;
34 }
35
36 return result;
37}
38
Time and Space Complexity
Time Complexity: O(n × log m)
or equivalently O(n × 32)
= O(n)
The algorithm iterates through the array nums
once from right to left (taking O(n)
time). For each element at position i
, it examines all 32 bits of the number (since we're dealing with 32-bit integers). The bit examination involves:
- Checking if bit
j
is set innums[i]
using(nums[i] >> j) & 1
-O(1)
operation - Updating the last occurrence position
f[j]
if the bit is set -O(1)
operation - Computing the maximum distance if the bit is not set but has been seen before -
O(1)
operation
Since we perform 32 constant-time operations for each of the n
elements, the total time complexity is O(n × 32)
= O(n × log m)
, where m
represents the maximum value in the array (as 32 bits can represent values up to 2^32 - 1
).
Space Complexity: O(n + log m)
or O(n + 32)
= O(n)
The algorithm uses:
- An array
ans
of sizen
to store the results -O(n)
space - An array
f
of fixed size 32 to track the last occurrence of each bit position -O(32)
=O(log m)
space - A few variables like
i
,j
,t
-O(1)
space
The total space complexity is O(n + 32)
= O(n)
since n
dominates the constant 32.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Bit Range Assumption
A common mistake is assuming that we only need to check a limited number of bits (e.g., only up to bit 20 or 25) instead of the full 32 bits. This can lead to incorrect results when dealing with larger numbers.
Pitfall Example:
# WRONG: Only checking 20 bits
for bit_position in range(20): # Missing bits 20-31
if (nums[current_index] >> bit_position) & 1:
rightmost_bit_position[bit_position] = current_index
Solution: Always check all 32 bits for signed 32-bit integers:
# CORRECT: Check all 32 bits
for bit_position in range(32):
if (nums[current_index] >> bit_position) & 1:
rightmost_bit_position[bit_position] = current_index
2. Forward Traversal Instead of Backward
Attempting to solve this problem by traversing from left to right makes it much harder to track which bits are needed for the maximum OR value.
Pitfall Example:
# WRONG: Forward traversal makes tracking complex
for i in range(n):
# Hard to know which bits appear in future positions
# Would require multiple passes or complex bookkeeping
Solution:
Use backward traversal so that when processing position i
, you already know all bits that appear in positions i+1
to n-1
:
# CORRECT: Backward traversal
for current_index in range(n - 1, -1, -1):
# We already know all bits that appear to the right
3. Off-by-One Error in Length Calculation
When calculating the subarray length from position i
to position j
, forgetting to add 1 is a frequent error.
Pitfall Example:
# WRONG: Missing +1 in length calculation distance_to_bit = rightmost_bit_position[bit_position] - current_index
Solution:
Remember that subarray length from index i
to index j
is j - i + 1
:
# CORRECT: Include both endpoints distance_to_bit = rightmost_bit_position[bit_position] - current_index + 1
4. Not Handling Single Element Case
Forgetting that each position can form a subarray of length 1 with just itself, which might be the answer if the element already contains all necessary bits.
Pitfall Example:
# WRONG: Starting with 0 or not initializing min_length = 0 # Wrong initial value
Solution: Always initialize minimum length to 1 since we're guaranteed to include at least the current element:
# CORRECT: Start with length 1 min_length = 1
5. Overwriting Bit Positions Prematurely
Updating the bit position tracker before calculating the required subarray length can lead to incorrect results.
Pitfall Example:
# WRONG: Updating before using the old value
for bit_position in range(32):
if (nums[current_index] >> bit_position) & 1:
rightmost_bit_position[bit_position] = current_index
# Now we can't use the old position for calculations!
if rightmost_bit_position[bit_position] != -1:
# This uses the updated value, not the original
distance_to_bit = rightmost_bit_position[bit_position] - current_index + 1
Solution: The correct approach already handles this properly by using if-elif structure:
# CORRECT: Check and update in proper order if (nums[current_index] >> bit_position) & 1: rightmost_bit_position[bit_position] = current_index elif rightmost_bit_position[bit_position] != -1: # Only executed if bit is NOT set in current number distance_to_bit = rightmost_bit_position[bit_position] - current_index + 1
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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
Want a Structured Path to Master System Design Too? Don’t Miss This!