Bitwise OR visualization

898: Bitwise ORs of Subarrays

Difficulty: Medium
Topics: Array, Dynamic Programming, Bit Manipulation
Companies: Google, Amazon, Microsoft
Pro Tip: For bitwise OR problems, remember that each element can add at most one new bit. The number of distinct OR values grows logarithmically with the maximum element value.

Problem Statement

Given an integer array arr, return the number of distinct bitwise ORs of all non-empty subarrays.

A subarray is a contiguous non-empty sequence of elements within the array. The bitwise OR of a subarray is the bitwise OR of all integers in that subarray.

Example 1

Input: arr = [0]

Output: 1

Explanation: Only one subarray [0] with OR value 0.

Example 2

Input: arr = [1,1,2]

Output: 3

Explanation: Distinct OR values: 1 (from [1]), 1 (from [1]), 2 (from [2]), 1 (from [1,1]), 3 (from [1,2]), 3 (from [1,1,2])

Example 3

Input: arr = [1,2,4]

Output: 6

Explanation: Distinct OR values: 1, 2, 4, 3 (1|2), 5 (1|4), 6 (2|4), 7 (1|2|4)

Problem Link: View on LeetCode ↗

Key Insight

The solution relies on two crucial observations:

  1. The number of distinct OR values for subarrays ending at index i is bounded by O(log(max_value))
  2. We can efficiently compute these values using dynamic programming and set operations
Important: The brute force approach (O(n²)) becomes infeasible for large arrays (n ≤ 50,000). We need an O(n log max) solution.

Brute Force Approach (TLE)

This approach generates every possible subarray and computes its bitwise OR:

Algorithm Steps

  1. Initialize an empty set to store distinct ORs
  2. For each starting index i:
    • Initialize current OR to 0
    • For each ending index j from i to end:
      • Compute OR = OR | arr[j]
      • Add OR to the set
  3. Return the size of the set

Why It Fails (TLE)

For an array of size n, there are O(n²) subarrays. When n = 50,000, this results in 1.25 billion operations, which is computationally infeasible.

Complexity Analysis

Time Complexity Space Complexity
O(n²) O(n²)
Brute Force Solution (TLE)
class Solution {
public:
    int subarrayBitwiseORs(vector<int>& arr) {
        unordered_set<int> st;
        int n = arr.size();
        
        for (int i = 0; i < n; i++) {
            int val = 0;
            for (int j = i; j < n; j++) {
                val |= arr[j];
                st.insert(val);
            }
        }
        return st.size();
    }
};
class Solution {
    public int subarrayBitwiseORs(int[] arr) {
        Set<Integer> st = new HashSet<>();
        int n = arr.length;
        
        for (int i = 0; i < n; i++) {
            int val = 0;
            for (int j = i; j < n; j++) {
                val |= arr[j];
                st.add(val);
            }
        }
        return st.size();
    }
}
class Solution:
    def subarrayBitwiseORs(self, arr: List[int]) -> int:
        st = set()
        n = len(arr)
        
        for i in range(n):
            val = 0
            for j in range(i, n):
                val |= arr[j]
                st.add(val)
                
        return len(st)
Warning: This solution will time out for large inputs (n > 1000) due to its O(n²) complexity.

Optimal Approach: Dynamic Programming with Sets

This approach leverages the insight that the number of distinct OR values for subarrays ending at index i is bounded by O(log(max_value)).

Algorithm Steps

  1. Initialize two sets: all_ors (global) and current (ORs ending at current index)
  2. For each number in array:
    • Create a new temporary set
    • Add current number to temporary set
    • For each value in previous set, OR with current number and add to temporary set
    • Set current to temporary set
    • Add all values from current to all_ors
  3. Return size of all_ors

Why This Works

Each step only considers ORs from the previous index, and the set size is bounded by O(log(max_value)). This reduces time complexity to O(n log max).

Complexity Analysis

Time Complexity Space Complexity
O(n log max) O(n log max)

Where max is the maximum element in the array (log max is about 30-32 for integers).

Optimal Solution
class Solution {
public:
    int subarrayBitwiseORs(vector<int>& arr) {
        unordered_set<int> all_ors;
        unordered_set<int> current;
        
        for (int num : arr) {
            unordered_set<int> temp;
            temp.insert(num);
            
            // Combine with previous ORs
            for (int val : current) {
                temp.insert(val | num);
            }
            
            current = temp;
            all_ors.insert(current.begin(), current.end());
        }
        
        return all_ors.size();
    }
};
class Solution {
    public int subarrayBitwiseORs(int[] arr) {
        Set<Integer> all_ors = new HashSet<>();
        Set<Integer> current = new HashSet<>();
        
        for (int num : arr) {
            Set<Integer> temp = new HashSet<>();
            temp.add(num);
            
            // Combine with previous ORs
            for (int val : current) {
                temp.add(val | num);
            }
            
            current = temp;
            all_ors.addAll(current);
        }
        
        return all_ors.size();
    }
}
class Solution:
    def subarrayBitwiseORs(self, arr: List[int]) -> int:
        all_ors = set()
        current = set()
        
        for num in arr:
            # Create new set with current number
            temp = {num}
            # Combine with previous ORs
            for val in current:
                temp.add(val | num)
                
            current = temp
            all_ors |= current  # Union with global set
            
        return len(all_ors)
Implementation Note: This solution efficiently handles large inputs by leveraging the logarithmic bound on distinct OR values.

Optimized Approach: Early Termination

This improvement adds early termination when no new bits are added.

Algorithm Steps

  1. Same as optimal approach, but track previous OR value
  2. If current set equals previous set, break early

Why This Helps

For arrays with large identical values, we can terminate early once no new OR values are generated.

Complexity Analysis

Time Complexity Space Complexity
O(n log max) O(n log max)

Same worst-case, but better for certain inputs.

Optimized with Early Termination
class Solution {
public:
    int subarrayBitwiseORs(vector<int>& arr) {
        unordered_set<int> all_ors;
        unordered_set<int> current;
        int last_size = 0;
        
        for (int num : arr) {
            unordered_set<int> temp;
            temp.insert(num);
            
            for (int val : current) {
                temp.insert(val | num);
            }
            
            // Early termination if no new ORs
            if (temp.size() == last_size && temp == current) {
                continue;
            }
            
            last_size = temp.size();
            current = temp;
            all_ors.insert(current.begin(), current.end());
        }
        
        return all_ors.size();
    }
};
class Solution {
    public int subarrayBitwiseORs(int[] arr) {
        Set<Integer> all_ors = new HashSet<>();
        Set<Integer> current = new HashSet<>();
        int last_size = 0;
        
        for (int num : arr) {
            Set<Integer> temp = new HashSet<>();
            temp.add(num);
            
            for (int val : current) {
                temp.add(val | num);
            }
            
            // Early termination if no new ORs
            if (temp.size() == last_size && temp.equals(current)) {
                continue;
            }
            
            last_size = temp.size();
            current = temp;
            all_ors.addAll(current);
        }
        
        return all_ors.size();
    }
}
class Solution:
    def subarrayBitwiseORs(self, arr: List[int]) -> int:
        all_ors = set()
        current = set()
        last_size = 0
        
        for num in arr:
            temp = {num}
            for val in current:
                temp.add(val | num)
                
            # Early termination if no new ORs
            if len(temp) == last_size and temp == current:
                continue
                
            last_size = len(temp)
            current = temp
            all_ors |= current
            
        return len(all_ors)
Implementation Note: This optimization helps for arrays with long sequences of identical values but doesn't improve worst-case complexity.

Edge Cases and Testing

1. All Zeros

Input: [0,0,0] → Output: 1
Explanation: Only OR value is 0

2. Increasing Powers of Two

Input: [1,2,4,8] → Output: 10
Explanation: OR values: 1,2,4,8,3,5,9,6,10,7,15

3. All Identical Values

Input: [3,3,3] → Output: 1
Explanation: Only OR value is 3

4. Mixed Values

Input: [1,3,5] → Output: 5
Explanation: OR values: 1,3,5,3,7,7

5. Large Input (50,000 elements)

Optimal solution handles in milliseconds
Brute force would take hours
Warning: Always test with worst-case inputs (large array with increasing powers of two) to ensure efficiency.

Related Topics Explained

Array

An array is a fundamental data structure consisting of a collection of elements identified by indices. In this problem:

Dynamic Programming

Dynamic programming solves complex problems by breaking them into simpler subproblems and storing their solutions:

Bit Manipulation

Bit manipulation involves direct bit-level operations:

Final Insight: This problem beautifully combines array traversal, dynamic programming state management, and bit manipulation properties to achieve an efficient solution where brute force fails.