Bitwise ORs of Subarrays
MedPrompt
Given an integer array arr, return the number of distinct bitwise ORs of all the non-empty subarrays of arr.
The bitwise OR of a subarray is the bitwise OR of each integer in the subarray. The bitwise OR of a subarray of one integer is that integer.
A subarray is a contiguous non-empty sequence of elements within an array.
Example 1:
Input: arr = [0]
Output: 1
Explanation: There is only one possible result: 0.Example 2:
Input: arr = [1,1,2]
Output: 3
Explanation: The possible subarrays are [1], [1], [2], [1, 1], [1, 2], [1, 1, 2].
These yield the results 1, 1, 2, 1, 3, 3.
There are 3 unique values, so the answer is 3.Example 3:
Input: arr = [1,2,4]
Output: 6
Explanation: The possible results are 1, 2, 3, 4, 6, and 7.
Constraints:
1 <= arr.length <= 5 * 1040 <= arr[i] <= 109
Approaches
2 approaches with complexity analysis and trade-offs.
This approach systematically finds every possible non-empty subarray, calculates its bitwise OR, and counts the number of unique results. A naive implementation would recompute the OR for each subarray from scratch, leading to an O(n^3) complexity. We can optimize this by observing that the OR of a subarray arr[i..j] is simply the OR of arr[i..j-1] and arr[j]. This allows us to compute the ORs for all subarrays starting at a given index i in a single pass, reducing the overall complexity.
Algorithm
-
- Initialize an empty
HashSetcalleddistinctOrsto store the unique bitwise OR values.
- Initialize an empty
-
- Iterate through the input array
arrwith an indexifrom0toarr.length - 1. This indexiwill be the starting point of our subarrays.
- Iterate through the input array
-
- Inside the first loop, initialize an integer
currentOrto0. This will store the bitwise OR of the subarray starting ati.
- Inside the first loop, initialize an integer
-
- Start a second, nested loop with an index
jfromitoarr.length - 1. This indexjwill be the ending point of our subarrays.
- Start a second, nested loop with an index
-
- In the inner loop, update
currentOrby performing a bitwise OR with the current element:currentOr |= arr[j].
- In the inner loop, update
-
- Add the
currentOrvalue to thedistinctOrsset. The set will only add the value if it's not already present.
- Add the
-
- After both loops complete, the
distinctOrsset contains all unique bitwise ORs of all possible subarrays.
- After both loops complete, the
-
- Return the size of the
distinctOrsset.
- Return the size of the
Walkthrough
We use two nested loops to iterate through all possible subarrays. The outer loop fixes the starting point i of the subarray, and the inner loop extends the subarray to the right by including elements from j = i to n-1.
A variable currentOr is used to maintain the bitwise OR of the current subarray arr[i..j]. As j increments, we update currentOr by taking the bitwise OR with the new element arr[j]. This avoids recalculating the OR from the beginning of the subarray each time.
All calculated currentOr values are stored in a HashSet to automatically handle uniqueness. The final answer is the size of this set.
import java.util.HashSet;import java.util.Set; class Solution { public int subarrayBitwiseORs(int[] arr) { Set<Integer> distinctOrs = new HashSet<>(); for (int i = 0; i < arr.length; i++) { int currentOr = 0; for (int j = i; j < arr.length; j++) { currentOr |= arr[j]; distinctOrs.add(currentOr); } } return distinctOrs.size(); }}Complexity
Time
`O(n^2)`, where `n` is the length of the array. The two nested loops iterate through all `n * (n + 1) / 2` subarrays, and the operation inside the inner loop is constant time.
Space
`O(D)`, where `D` is the number of distinct OR values. In the worst-case scenario, `D` can be on the order of `O(n^2)`, for example, if every subarray produces a unique OR value.
Trade-offs
Pros
Simple to understand and implement.
More efficient than the naive
O(n^3)approach.
Cons
The
O(n^2)time complexity is too slow for the given constraints (n <= 5 * 10^4), leading to a "Time Limit Exceeded" error on larger test cases.
This approach uses a dynamic programming-like strategy combined with a key insight about the bitwise OR operation. We process the array element by element, maintaining a set of all possible OR values for subarrays ending at the current position. The crucial observation is that this set of values is surprisingly small.
Algorithm
-
- Initialize a
HashSettotalDistinctOrsto store the final unique ORs from all subarrays.
- Initialize a
-
- Initialize another
HashSetcurrentOrsto store the unique ORs of subarrays ending at the previous element.
- Initialize another
-
- Iterate through each number
numin the input arrayarr.
- Iterate through each number
-
- Inside the loop, create a new temporary
HashSetnextOrs. This set will store the unique ORs of subarrays ending at the current elementnum.
- Inside the loop, create a new temporary
-
- Add
numitself tonextOrs, as it represents the OR of the subarray containing onlynum.
- Add
-
- Iterate through each
prevOrvalue incurrentOrs. For eachprevOr, calculateprevOr | numand add the result tonextOrs. This computes the ORs for all subarrays ending at the current position that are longer than one element.
- Iterate through each
-
- After the inner loop,
nextOrscontains all unique ORs for subarrays ending atnum. UpdatecurrentOrsto benextOrsfor the next iteration.
- After the inner loop,
-
- Add all elements from the new
currentOrstototalDistinctOrs.
- Add all elements from the new
-
- After iterating through the entire array, return the size of
totalDistinctOrs.
- After iterating through the entire array, return the size of
Walkthrough
Let R_i be the set of bitwise ORs of all subarrays ending at index i. We can compute R_i based on R_{i-1}:
R_i = { x | arr[i] for x in R_{i-1} } U { arr[i] }
This means the ORs for subarrays ending at i are either arr[i] itself (for the subarray [arr[i]]) or the result of ORing arr[i] with an OR from a subarray ending at i-1.
The key insight is that the size of R_i is bounded. Consider the sequence of values OR(arr[i..i]), OR(arr[i-1..i]), OR(arr[i-2..i]), ... . This sequence is non-decreasing because a | b >= a. For a value in this sequence to be strictly greater than the previous one, at least one new bit must be set to 1. Since the input numbers are less than or equal to 10^9 (which is less than 2^30), they can be represented by at most 30 bits. Therefore, the number of distinct values in this sequence is at most 30. This implies |R_i| <= 30.
This bound allows for an efficient algorithm. We iterate through the array, and at each step i, we compute R_i from R_{i-1}. Since |R_{i-1}| is small (at most 30), this computation is very fast. We collect all values from all R_i sets into a global set to find the total number of distinct ORs.
import java.util.HashSet;import java.util.Set; class Solution { public int subarrayBitwiseORs(int[] arr) { Set<Integer> totalDistinctOrs = new HashSet<>(); Set<Integer> currentOrs = new HashSet<>(); for (int num : arr) { Set<Integer> nextOrs = new HashSet<>(); nextOrs.add(num); for (int prevOr : currentOrs) { nextOrs.add(prevOr | num); } currentOrs = nextOrs; totalDistinctOrs.addAll(currentOrs); } return totalDistinctOrs.size(); }}Complexity
Time
`O(n * W)`, where `n` is the length of the array and `W` is the number of bits in the integer type (e.g., 30-32). For each of the `n` elements, we iterate through `currentOrs`. Due to the bitwise OR property, the size of `currentOrs` is bounded by `W`. Thus, the inner loop runs at most `W` times.
Space
`O(n * W)`. The space is dominated by `totalDistinctOrs`. In the worst case, we might add up to `W` new distinct values at each of the `n` steps. The `currentOrs` and `nextOrs` sets require `O(W)` space.
Trade-offs
Pros
Very efficient, easily passing the time constraints.
Leverages a clever property of the bitwise OR operation.
Cons
The reasoning behind the complexity bound is less intuitive than the brute-force approach.
The space complexity, while acceptable, can be larger than the brute-force approach in some average cases, although its worst-case bound is better (
O(n*W)vsO(n^2)).
Solutions
Solution
class Solution {public int subarrayBitwiseORs(int[] arr) { Set<Integer> s = new HashSet<>(); s.add(0); Set<Integer> ans = new HashSet<>(); for (int x : arr) { Set<Integer> t = new HashSet<>(); for (int y : s) { t.add(x | y); } t.add(x); s = t; ans.addAll(s); } return ans.size(); }}class Solution {public: int subarrayBitwiseORs(vector<int> &arr) { unordered_set<int> s{{0}}; unordered_set<int> ans; for (int &x : arr) { unordered_set<int> t{{x}}; for (int y : s) { t.insert(x | y); } s = move(t); ans.insert(s.begin(), s.end()); } return ans.size(); }};class Solution: def subarrayBitwiseORs(self, arr: List[int]) -> int: s = {0} ans = set() for x in arr: s = {x | y for y in s} | {x} ans |= s return len(ans)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.