2505. Bitwise OR of All Subsequence Sums 🔒
Problem Description
Given an integer array nums
, you need to find the bitwise OR of the sums of all possible subsequences of the array.
A subsequence is a sequence that can be derived from the original array by removing zero or more elements while maintaining the relative order of the remaining elements. For example, if nums = [1, 2, 3]
, the possible subsequences are: []
, [1]
, [2]
, [3]
, [1,2]
, [1,3]
, [2,3]
, and [1,2,3]
.
For each subsequence, calculate its sum, then take the bitwise OR of all these sums to get the final result.
How the solution works:
The key insight is that we don't need to generate all subsequences explicitly. Instead, we can track how each bit position contributes to the final OR result.
The solution uses an array cnt
of size 64 to count how many times each bit position can be set to 1 across all possible subsequence sums.
For each number in nums
, we check which bit positions are set to 1 (using (v >> i) & 1
) and increment the corresponding counter in cnt[i]
.
Then, we process the counts from the lowest bit to the highest:
- If
cnt[i] > 0
, it means at least one subsequence sum has biti
set to 1, so we include1 << i
in our answer using OR operation - We handle carry-over by adding
cnt[i] // 2
tocnt[i + 1]
, since every two occurrences of a bit at positioni
create one occurrence at positioni + 1
when summing
This approach efficiently computes the bitwise OR of all subsequence sums without explicitly generating all 2^n
subsequences.
Intuition
The brute force approach would be to generate all 2^n
subsequences, calculate their sums, and then OR all these sums together. However, this would be exponentially slow.
The key observation is that for bitwise OR operations, we only care about whether a particular bit position is ever set to 1 in any of the subsequence sums. Once a bit is set to 1 in the OR result, it stays 1 regardless of how many more times we encounter it.
Let's think about how bits contribute to subsequence sums:
- If a number has bit
i
set to 1, it contributes2^i
to any subsequence sum that includes it - Multiple numbers with the same bit set will contribute multiple times
2^i
to different subsequence sums - When we add values like
2^i + 2^i = 2^(i+1)
, carries can propagate to higher bit positions
For example, if we have two numbers with bit 0 set (contributing value 1 each), they can appear together in some subsequences creating sums with bit 1 set (since 1 + 1 = 2 = 2^1
).
This leads us to the counting approach:
- First, count how many numbers have each bit position set - this tells us the potential contributions at each bit level
- For each bit position, if at least one number contributes to it (directly or through carry), that bit will appear in some subsequence sum, so we include it in our OR result
- Handle carry propagation: if we have
k
contributions at bit positioni
, they can combine in various ways across different subsequences. The key insight is thatk
contributions at positioni
can createk // 2
carries to positioni+1
By processing bits from low to high and propagating carries, we capture all possible bit positions that can appear in any subsequence sum, giving us the final OR result without explicitly computing all subsequences.
Learn more about Math and Prefix Sum patterns.
Solution Approach
The solution uses bit manipulation to efficiently compute the result without generating all subsequences.
Data Structure:
- An array
cnt
of size 64 to track the count of contributions at each bit position (64 bits is sufficient for handling carries from 32-bit integers)
Algorithm Steps:
-
Count bit contributions from original numbers:
for v in nums: for i in range(31): if (v >> i) & 1: cnt[i] += 1
- For each number
v
in the array, check each of its 31 bit positions (0 to 30) - Use right shift
v >> i
and bitwise AND with 1 to check if biti
is set - If bit
i
is set, incrementcnt[i]
to record this contribution
- For each number
-
Process counts and handle carries:
for i in range(63): if cnt[i]: ans |= 1 << i cnt[i + 1] += cnt[i] // 2
- Iterate through bit positions from 0 to 62
- If
cnt[i] > 0
, it means at least one subsequence sum will have biti
set, so we OR1 << i
into our answer - Handle carry propagation:
cnt[i] // 2
represents how many carries go to the next bit position- This works because when summing binary numbers, every two 1s at position
i
create one 1 at positioni+1
- For example, if 3 subsequences have bit 2 set, when summed in certain combinations, they create
3 // 2 = 1
carry to bit 3
- This works because when summing binary numbers, every two 1s at position
-
Return the result:
- The variable
ans
accumulates all bit positions that appear in at least one subsequence sum - The final
ans
is the bitwise OR of all possible subsequence sums
- The variable
Why this works:
- Each number can be selected or not selected in a subsequence, creating
2^n
total subsequences - The
cnt
array captures how bits from different numbers can combine across all these subsequences - The carry propagation ensures we account for sums where multiple numbers with the same bit set create carries to higher positions
- Since OR only cares about whether a bit is ever 1, we just need to know if each bit position is reachable, which this counting approach determines efficiently
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [1, 3]
.
Step 1: Identify all subsequences and their sums
- Subsequences:
[]
,[1]
,[3]
,[1,3]
- Sums:
0
,1
,3
,4
- Expected result:
0 | 1 | 3 | 4 = 7
Step 2: Count bit contributions
Initialize cnt = [0] * 64
For v = 1
(binary: 001
):
- Bit 0 is set:
cnt[0] += 1
→cnt = [1, 0, 0, ...]
For v = 3
(binary: 011
):
- Bit 0 is set:
cnt[0] += 1
→cnt = [2, 0, 0, ...]
- Bit 1 is set:
cnt[1] += 1
→cnt = [2, 1, 0, ...]
Step 3: Process counts and handle carries
Initialize ans = 0
-
i = 0
:cnt[0] = 2 > 0
, soans |= 1 << 0 = 1
→ans = 1
- Carry:
cnt[1] += 2 // 2 = 1
→cnt = [2, 2, 0, ...]
-
i = 1
:cnt[1] = 2 > 0
, soans |= 1 << 1 = 2
→ans = 3
- Carry:
cnt[2] += 2 // 2 = 1
→cnt = [2, 2, 1, 0, ...]
-
i = 2
:cnt[2] = 1 > 0
, soans |= 1 << 2 = 4
→ans = 7
- Carry:
cnt[3] += 1 // 2 = 0
→cnt = [2, 2, 1, 0, ...]
-
i = 3
and beyond:cnt[i] = 0
, so no more contributions
Result: ans = 7
Why the carries work:
cnt[0] = 2
means two numbers have bit 0 set (both 1 and 3)- When both appear in subsequence
[1,3]
, we get1 + 3 = 4 = 100₂
- This creates a carry that eventually sets bit 2 in the sum
- The algorithm captures this through carry propagation: the two contributions at bit 0 create a carry to bit 1, and combined with the existing bit 1 from value 3, we get another carry to bit 2
Solution Implementation
1class Solution:
2 def subsequenceSumOr(self, nums: List[int]) -> int:
3 # Initialize bit count array for up to 64 bits
4 # bit_count[i] represents how many times bit i appears across all numbers
5 bit_count = [0] * 64
6 result = 0
7
8 # Count the occurrence of each bit position across all numbers
9 for number in nums:
10 for bit_position in range(31):
11 # Check if the bit at position 'bit_position' is set
12 if (number >> bit_position) & 1:
13 bit_count[bit_position] += 1
14
15 # Process bit counts to determine which bits can appear in subsequence sums
16 for bit_position in range(63):
17 # If this bit position has any count, it can appear in some subsequence sum
18 if bit_count[bit_position]:
19 result |= 1 << bit_position
20
21 # Carry over: pairs of bits at position i create one bit at position i+1
22 # This simulates the addition carry mechanism
23 bit_count[bit_position + 1] += bit_count[bit_position] // 2
24
25 return result
26
1class Solution {
2 public long subsequenceSumOr(int[] nums) {
3 // Array to count the contribution of each bit position (0-63)
4 // cnt[i] represents how many times bit i appears across all possible sums
5 long[] bitCount = new long[64];
6 long result = 0;
7
8 // Step 1: Count the occurrence of each bit position in all numbers
9 for (int num : nums) {
10 // Check each bit position (0-30) in the current number
11 for (int bitPosition = 0; bitPosition < 31; bitPosition++) {
12 // If the bit at position 'bitPosition' is set to 1
13 if (((num >> bitPosition) & 1) == 1) {
14 bitCount[bitPosition]++;
15 }
16 }
17 }
18
19 // Step 2: Process bit counts and calculate the final OR result
20 for (int bitPosition = 0; bitPosition < 63; bitPosition++) {
21 // If this bit position appears in any subsequence sum, set it in result
22 if (bitCount[bitPosition] > 0) {
23 result |= 1L << bitPosition;
24 }
25 // Carry over: every 2 occurrences of bit i contribute to 1 occurrence of bit i+1
26 // This simulates addition carry in binary representation
27 bitCount[bitPosition + 1] += bitCount[bitPosition] / 2;
28 }
29
30 return result;
31 }
32}
33
1class Solution {
2public:
3 long long subsequenceSumOr(vector<int>& nums) {
4 // Array to count the contribution of each bit position (0-63)
5 // cnt[i] represents how many times bit i appears across all numbers
6 vector<long long> bitCount(64);
7 long long result = 0;
8
9 // Step 1: Count the occurrence of each bit across all numbers
10 for (int num : nums) {
11 // Check each bit position (0-30) in the current number
12 for (int bitPos = 0; bitPos < 31; ++bitPos) {
13 // If bit at position bitPos is set in num
14 if ((num >> bitPos) & 1) {
15 ++bitCount[bitPos];
16 }
17 }
18 }
19
20 // Step 2: Process bit counts and build the result
21 for (int bitPos = 0; bitPos < 63; ++bitPos) {
22 // If this bit position has any contribution, set it in result
23 if (bitCount[bitPos] > 0) {
24 result |= (1LL << bitPos);
25 }
26 // Propagate carry to next bit position
27 // When we have multiple instances of the same bit, they sum up
28 // and can create a carry to the next higher bit
29 bitCount[bitPos + 1] += bitCount[bitPos] / 2;
30 }
31
32 return result;
33 }
34};
35
1function subsequenceSumOr(nums: number[]): number {
2 // Array to count the contribution of each bit position (0-63)
3 // bitCount[i] represents how many times bit i appears across all numbers
4 const bitCount: number[] = new Array(64).fill(0);
5 let result: number = 0;
6
7 // Step 1: Count the occurrence of each bit across all numbers
8 for (const num of nums) {
9 // Check each bit position (0-30) in the current number
10 for (let bitPos = 0; bitPos < 31; bitPos++) {
11 // If bit at position bitPos is set in num
12 if ((num >> bitPos) & 1) {
13 bitCount[bitPos]++;
14 }
15 }
16 }
17
18 // Step 2: Process bit counts and build the result
19 for (let bitPos = 0; bitPos < 63; bitPos++) {
20 // If this bit position has any contribution, set it in result
21 if (bitCount[bitPos] > 0) {
22 // Use BigInt for bit manipulation beyond 32 bits, then convert back
23 result |= (1 << bitPos);
24 }
25 // Propagate carry to next bit position
26 // When we have multiple instances of the same bit, they sum up
27 // and can create a carry to the next higher bit
28 bitCount[bitPos + 1] += Math.floor(bitCount[bitPos] / 2);
29 }
30
31 return result;
32}
33
Time and Space Complexity
Time Complexity: O(n × log M)
where n
is the length of the array and M
is the maximum value in the array.
- The outer loop iterates through all
n
elements in thenums
array. - For each element
v
, the inner loop iterates through 31 bit positions (since we're checking up to bit position 30, which covers all 32-bit integers). - The bit position check corresponds to
log M
whereM
is the maximum value, as we need to check all bits up to the highest set bit in the maximum value. - The second loop iterates through 63 positions with constant time operations, contributing
O(63) = O(1)
. - Overall:
O(n × 31) = O(n × log M)
.
Space Complexity: O(1)
- The
cnt
array has a fixed size of 64 elements regardless of input size. - The
ans
variable uses constant space. - No additional data structures that scale with input size are used.
- Therefore, the space complexity is
O(64) = O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Bit Range Assumptions
A common mistake is assuming that 31 bits (for 32-bit integers) is sufficient for the bit count array. However, when summing multiple numbers, carries can propagate beyond the original bit width.
Pitfall Example:
# WRONG: Only allocating 32 bits
bit_count = [0] * 32 # This can cause index out of bounds
for bit_position in range(31): # Processing only up to bit 30
if bit_count[bit_position]:
result |= 1 << bit_position
bit_count[bit_position + 1] += bit_count[bit_position] // 2 # Error when bit_position = 30
Solution: Always allocate extra space for carry propagation. Use 64 bits to handle carries from 32-bit integers safely:
bit_count = [0] * 64 # Sufficient space for carries
for bit_position in range(63): # Process up to bit 62
# ... rest of the logic
2. Misunderstanding Carry Propagation Logic
Some may incorrectly think that if a bit appears k
times, it contributes k
to the OR result, or forget to propagate carries entirely.
Pitfall Example:
# WRONG: Directly ORing the count value
for bit_position in range(63):
if bit_count[bit_position]:
result |= bit_count[bit_position] << bit_position # Incorrect!
# Missing carry propagation
Solution: Remember that OR only cares about presence (0 or 1), not the count. The carry propagation simulates how bits combine during addition:
for bit_position in range(63):
if bit_count[bit_position]: # Any non-zero count sets this bit
result |= 1 << bit_position # Set only the single bit
bit_count[bit_position + 1] += bit_count[bit_position] // 2 # Propagate carries
3. Off-by-One Errors in Loop Boundaries
Confusion about whether to process bits 0-30 or 0-31, and whether to iterate to 62 or 63 in the final loop.
Pitfall Example:
# WRONG: Inconsistent boundaries
for number in nums:
for bit_position in range(32): # Should be 31 for 0-indexed positions
if (number >> bit_position) & 1:
bit_count[bit_position] += 1
for bit_position in range(64): # Should be 63 to avoid accessing bit_count[64]
if bit_count[bit_position]:
result |= 1 << bit_position
bit_count[bit_position + 1] += bit_count[bit_position] // 2 # Index error!
Solution: Be consistent with boundaries:
- For 32-bit integers, check bits 0-30 (31 positions total)
- In the carry propagation loop, iterate from 0 to 62 to avoid accessing
bit_count[64]
for bit_position in range(31): # Bits 0-30 for 32-bit integers
# ...
for bit_position in range(63): # Process up to 62, access up to bit_count[63]
# ...
Which of the following array represent a max heap?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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!