questions B (backspace - burst-balloons)

This commit is contained in:
2025-05-24 22:06:49 +01:00
parent f757e28b24
commit 2123791ec3
67 changed files with 13945 additions and 0 deletions

View File

@@ -0,0 +1,175 @@
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:
- dynamic-programming
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)
&nbsp;
**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
&nbsp;
**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
&nbsp;
**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.