201 lines
8.7 KiB
YAML
201 lines
8.7 KiB
YAML
title: Bitwise ORs of Subarrays
|
|
slug: bitwise-ors-of-subarrays
|
|
difficulty: medium
|
|
leetcode_id: 898
|
|
leetcode_url: https://leetcode.com/problems/bitwise-ors-of-subarrays/
|
|
categories:
|
|
- arrays
|
|
- dynamic-programming
|
|
- math
|
|
patterns:
|
|
- slug: dynamic-programming
|
|
is_optimal: true
|
|
|
|
function_signature: "def subarray_bitwise_ors(arr: list[int]) -> int:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { arr: [0] }
|
|
expected: 1
|
|
- input: { arr: [1, 1, 2] }
|
|
expected: 3
|
|
- input: { arr: [1, 2, 4] }
|
|
expected: 6
|
|
hidden:
|
|
- input: { arr: [1] }
|
|
expected: 1
|
|
- input: { arr: [0, 0, 0] }
|
|
expected: 1
|
|
- input: { arr: [1, 2, 3, 4, 5] }
|
|
expected: 7
|
|
- input: { arr: [7, 7, 7] }
|
|
expected: 1
|
|
- input: { arr: [1, 3, 5, 7] }
|
|
expected: 4
|
|
- input: { arr: [8, 4, 2, 1] }
|
|
expected: 10
|
|
|
|
description: |
|
|
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.
|
|
|
|
constraints: |
|
|
- `1 <= arr.length <= 5 * 10^4`
|
|
- `0 <= arr[i] <= 10^9`
|
|
|
|
examples:
|
|
- input: "arr = [0]"
|
|
output: "1"
|
|
explanation: "There is only one possible result: 0."
|
|
- 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."
|
|
- input: "arr = [1,2,4]"
|
|
output: "6"
|
|
explanation: "The possible results are 1, 2, 3, 4, 6, and 7."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Think of this problem like building up OR values layer by layer.
|
|
|
|
The key insight is understanding how bitwise OR behaves: once a bit is set to `1`, it stays `1` no matter what you OR it with. This means as we extend a subarray by adding more elements, the OR value can only **stay the same or increase** — it can never decrease.
|
|
|
|
Imagine you're at position `i` in the array. You want to know all possible OR values for subarrays **ending at position `i`**. These come from two sources:
|
|
- The element `arr[i]` itself (the subarray of length 1)
|
|
- Taking each OR value from subarrays ending at `i-1` and OR-ing it with `arr[i]`
|
|
|
|
Here's the clever part: even though there could be many subarrays ending at `i-1`, the number of **distinct** OR values is limited. Since OR can only add bits (never remove them), and numbers up to `10^9` have at most 30 bits, there can be at most 30 distinct OR values for subarrays ending at any position.
|
|
|
|
This transforms what seems like an O(n^2) problem into something much more efficient.
|
|
|
|
approach: |
|
|
We solve this using a **Dynamic Set Approach**:
|
|
|
|
**Step 1: Initialise data structures**
|
|
|
|
- `result`: A set to collect all distinct OR values we find across all subarrays
|
|
- `prev`: A set to track distinct OR values of subarrays ending at the previous position (starts empty)
|
|
|
|
|
|
|
|
**Step 2: Iterate through each element**
|
|
|
|
- For each element `num` in the array:
|
|
- Create a new set `curr` for OR values of subarrays ending at this position
|
|
- Add `num` itself (the single-element subarray)
|
|
- For each OR value `v` in `prev`, compute `v | num` and add to `curr`
|
|
- This gives us all distinct OR values for subarrays ending at the current position
|
|
|
|
|
|
|
|
**Step 3: Update tracking sets**
|
|
|
|
- Add all values from `curr` to our global `result` set
|
|
- Set `prev = curr` for the next iteration
|
|
- The size of `curr` is bounded by the number of bits (at most 30), keeping this efficient
|
|
|
|
|
|
|
|
**Step 4: Return the result**
|
|
|
|
- Return the size of `result`, which contains all distinct OR values from all subarrays
|
|
|
|
common_pitfalls:
|
|
- title: The Brute Force Trap
|
|
description: |
|
|
The naive approach generates all O(n^2) subarrays and computes each OR:
|
|
- Outer loop for start index
|
|
- Inner loop for end index
|
|
- Compute OR for each subarray
|
|
|
|
With `n = 5 * 10^4`, this means up to 2.5 billion operations, causing **Time Limit Exceeded**.
|
|
|
|
The insight that saves us is recognizing that distinct OR values are bounded by the bit width, not by the number of subarrays.
|
|
wrong_approach: "Generate all subarrays and compute OR for each"
|
|
correct_approach: "Track only distinct OR values ending at each position"
|
|
|
|
- title: Missing the Monotonicity of OR
|
|
description: |
|
|
A common oversight is not realizing that OR values can only grow (or stay same) as subarrays extend.
|
|
|
|
For example: `1 | 2 = 3`, `3 | 4 = 7`, `7 | 8 = 15`. Each step either adds new bits or keeps existing ones.
|
|
|
|
This monotonicity means that for subarrays ending at position `i`, there are at most `log(max_value) + 1` distinct values (about 30 for values up to `10^9`).
|
|
|
|
Understanding this is crucial for seeing why the "set of sets" approach is efficient.
|
|
wrong_approach: "Assuming O(n) distinct values per position"
|
|
correct_approach: "Recognizing at most O(log(max_value)) distinct values per position"
|
|
|
|
- title: Using a List Instead of a Set
|
|
description: |
|
|
If you track OR values in a list instead of a set, you'll get duplicate values that slow down the algorithm.
|
|
|
|
For `arr = [1, 1, 1, 1]`, using lists would accumulate values like: `[1]`, `[1, 1]`, `[1, 1, 1]`, etc.
|
|
|
|
Using sets automatically deduplicates, keeping the working set small.
|
|
|
|
key_takeaways:
|
|
- "**Bitwise OR monotonicity**: OR can only set bits, never clear them, so values only grow as subarrays extend"
|
|
- "**Bounded distinct values**: For numbers with `b` bits, at most `b` distinct OR values exist for subarrays ending at any position"
|
|
- "**Dynamic set pattern**: Track distinct values at each step instead of all subarrays — useful for many bit manipulation problems"
|
|
- "**Related problems**: Similar techniques apply to Bitwise AND of subarrays, maximum XOR of subarrays, and other aggregate operations"
|
|
|
|
time_complexity: "O(n * log(max_value)). For each of the `n` elements, we process at most `log(max_value)` distinct OR values (about 30 for values up to `10^9`)."
|
|
space_complexity: "O(n * log(max_value)). The result set can contain at most `n * log(max_value)` distinct values, though in practice it's often much smaller."
|
|
|
|
solutions:
|
|
- approach_name: Dynamic Set
|
|
is_optimal: true
|
|
code: |
|
|
def subarray_bitwise_ors(arr: list[int]) -> int:
|
|
# Collect all distinct OR values across all subarrays
|
|
result = set()
|
|
# Track distinct OR values of subarrays ending at previous position
|
|
prev = set()
|
|
|
|
for num in arr:
|
|
# OR values for subarrays ending at current position:
|
|
# 1. The element itself (single-element subarray)
|
|
# 2. Each previous OR value combined with current element
|
|
curr = {num} | {v | num for v in prev}
|
|
|
|
# Add current position's values to global result
|
|
result |= curr
|
|
# Current becomes previous for next iteration
|
|
prev = curr
|
|
|
|
return len(result)
|
|
explanation: |
|
|
**Time Complexity:** O(n * log(max_value)) — For each element, we process at most 30 distinct OR values.
|
|
|
|
**Space Complexity:** O(n * log(max_value)) — The result set stores all distinct OR values.
|
|
|
|
This approach exploits the key insight that bitwise OR is monotonically increasing. As we extend subarrays, OR values can only grow or stay the same. Since values are bounded by `10^9` (about 30 bits), there are at most 30 distinct OR values for subarrays ending at any position.
|
|
|
|
- approach_name: Brute Force
|
|
is_optimal: false
|
|
code: |
|
|
def subarray_bitwise_ors(arr: list[int]) -> int:
|
|
result = set()
|
|
n = len(arr)
|
|
|
|
# Try every possible subarray
|
|
for i in range(n):
|
|
or_value = 0
|
|
# Extend subarray from position i
|
|
for j in range(i, n):
|
|
# Accumulate OR as we extend
|
|
or_value |= arr[j]
|
|
result.add(or_value)
|
|
|
|
return len(result)
|
|
explanation: |
|
|
**Time Complexity:** O(n^2) — Nested loops to consider all subarrays.
|
|
|
|
**Space Complexity:** O(n * log(max_value)) — Result set stores distinct OR values.
|
|
|
|
This approach is conceptually simple but too slow for large inputs. It generates all O(n^2) subarrays and computes their OR values. While we optimize slightly by accumulating OR as we extend (rather than recomputing), it still times out for `n = 5 * 10^4`. Included to illustrate why the dynamic set approach is necessary.
|