2044. Count Number of Maximum Bitwise-OR Subsets
Problem Description
You are given an integer array nums
. Your task is to find the maximum possible bitwise OR value that can be obtained from any subset of nums
, and then count how many different non-empty subsets can produce this maximum OR value.
A subset is formed by selecting some (or all) elements from the original array, where the selected elements maintain their original values. Two subsets are considered different if they are formed by selecting elements at different indices, even if the resulting values are the same.
The bitwise OR of a subset is calculated by applying the OR operation to all elements in that subset. For example, if a subset contains elements [a, b, c]
, its bitwise OR would be a OR b OR c
.
The solution uses a depth-first search (DFS) approach to explore all possible subsets. First, it calculates the maximum possible OR value mx
by performing OR on all elements in the array (since OR operations can only maintain or increase the number of set bits, never decrease them). Then, it uses DFS to enumerate all possible subsets and counts those whose OR value equals mx
.
The DFS function dfs(i, t)
tracks:
i
: the current index in the array being consideredt
: the current OR value accumulated so far
At each step, the algorithm makes two choices:
- Skip the current element:
dfs(i + 1, t)
- Include the current element:
dfs(i + 1, t | nums[i])
When all elements have been considered (i == len(nums)
), if the accumulated OR value t
equals the maximum mx
, the answer counter is incremented.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- No: The problem involves an integer array and subsets, not nodes and edges as in graph problems.
Need to solve for kth smallest/largest?
- No: The problem is not about finding the kth smallest or largest element, but rather counting subsets with a specific property.
Involves Linked Lists?
- No: The problem deals with an integer array, not linked lists.
Does the problem have small constraints?
- Yes: Looking at typical constraints for this problem (array length usually ≤ 16), and the fact that we need to enumerate all possible subsets (2^n possibilities), the constraints are intentionally kept small to allow exhaustive enumeration.
Brute force / Backtracking?
- Yes: Since we need to explore all possible subsets to find those with maximum bitwise OR value, and the constraints are small enough to allow checking all 2^n subsets, a brute force/backtracking approach is appropriate.
Conclusion: The flowchart correctly leads us to use a Backtracking approach. This makes sense because:
- We need to enumerate all possible subsets (a classic backtracking use case)
- The small constraints make exhaustive search feasible
- At each position in the array, we make a choice: include or exclude the current element
- We track the OR value as we build each subset and count those matching the maximum
The DFS implementation in the solution is a form of backtracking where we explore both branches (include/exclude) at each decision point.
Intuition
The key insight is understanding what makes the maximum bitwise OR value. When we perform OR operations on numbers, we can only "turn on" bits (change 0 to 1), never "turn off" bits (change 1 to 0). This means the maximum possible OR value would have the most bits turned on.
Think about it this way: if we have nums = [3, 2, 1, 5]
, their binary representations are:
- 3 =
011
- 2 =
010
- 1 =
001
- 5 =
101
When we OR all of them together: 011 | 010 | 001 | 101 = 111 = 7
, we get the maximum possible OR value because we've collected all the unique bit positions that appear anywhere in the array.
This leads to our first realization: the maximum OR value is simply the OR of all elements in the array. We can't get a higher value by selecting a subset because we can't introduce new 1-bits that don't exist in any element.
Now, the question becomes: how many subsets can achieve this maximum? Since we need to count all possible subsets, and with small constraints (typically n ≤ 16), we can afford to check all 2^n possible subsets.
The backtracking approach naturally fits here because subset generation is a series of binary decisions: for each element, we either include it or exclude it. We can visualize this as a binary tree where:
- At each level, we decide about one element
- Left branch = exclude the element
- Right branch = include the element
As we traverse this decision tree using DFS, we maintain the current OR value. When we reach a leaf (processed all elements), we check if our accumulated OR equals the maximum. If yes, we've found one valid subset.
The elegance of this solution is that it combines two simple ideas:
- Calculate the target (maximum OR) upfront in O(n) time
- Use DFS to systematically generate and check all subsets
This way, we avoid storing all subsets in memory - we just count them as we go.
Learn more about Backtracking patterns.
Solution Approach
The implementation follows a depth-first search (DFS) pattern to enumerate all possible subsets. Let's walk through the key components:
Step 1: Calculate the Maximum OR Value
mx = reduce(lambda x, y: x | y, nums)
We first compute the maximum possible OR value by performing bitwise OR on all elements. This gives us our target value since OR operations can only maintain or add 1-bits, never remove them.
Step 2: Define the DFS Function
def dfs(i, t):
nonlocal ans, mx
if i == len(nums):
if t == mx:
ans += 1
return
dfs(i + 1, t)
dfs(i + 1, t | nums[i])
The DFS function takes two parameters:
i
: Current index in the array we're consideringt
: Current bitwise OR value accumulated so far
Step 3: Base Case
When i == len(nums)
, we've made decisions for all elements. At this point:
- We check if our accumulated OR value
t
equals the maximummx
- If yes, we increment our answer counter
Step 4: Recursive Cases
For each element at index i
, we explore two branches:
- Exclude the element:
dfs(i + 1, t)
- Move to the next index without changing the OR value - Include the element:
dfs(i + 1, t | nums[i])
- Move to the next index and update OR value by includingnums[i]
Step 5: Initialize and Execute
ans = 0 mx = reduce(lambda x, y: x | y, nums) dfs(0, 0) return ans
We initialize:
ans = 0
: Counter for valid subsets- Start DFS from index 0 with initial OR value of 0
- Return the final count
Time Complexity: O(2^n) where n is the length of nums, as we explore all possible subsets.
Space Complexity: O(n) for the recursion stack depth.
The beauty of this approach is that we don't need to store all subsets - we generate them implicitly through our recursive calls and only count those matching our criteria. The nonlocal
keyword allows the nested function to modify the ans
variable in the outer scope, accumulating our count across all recursive calls.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with nums = [3, 1]
to illustrate how the solution works.
Step 1: Calculate Maximum OR Value
- Binary representations: 3 =
11
, 1 =01
- Maximum OR = 3 | 1 =
11
|01
=11
= 3 - So our target value
mx = 3
Step 2: DFS Exploration
Starting with dfs(0, 0)
, we'll trace through all recursive calls:
dfs(0, 0) [Starting at index 0, current OR = 0] ├── dfs(1, 0) [Skip nums[0]=3, OR stays 0] │ ├── dfs(2, 0) [Skip nums[1]=1, OR stays 0] │ │ └── Base case: i=2, t=0 ≠ mx=3, don't count │ └── dfs(2, 0|1=1) [Include nums[1]=1, OR becomes 1] │ └── Base case: i=2, t=1 ≠ mx=3, don't count └── dfs(1, 0|3=3) [Include nums[0]=3, OR becomes 3] ├── dfs(2, 3) [Skip nums[1]=1, OR stays 3] │ └── Base case: i=2, t=3 = mx=3, count! ans=1 └── dfs(2, 3|1=3) [Include nums[1]=1, OR stays 3] └── Base case: i=2, t=3 = mx=3, count! ans=2
Step 3: Result Analysis We found 2 subsets that produce the maximum OR value of 3:
[3]
- Just element at index 0[3, 1]
- Elements at indices 0 and 1
The answer is 2.
Notice how:
- The subset
[1]
gives OR = 1, which is less than maximum - The empty subset gives OR = 0, which is less than maximum
- Only subsets containing the element 3 can achieve the maximum OR value of 3, since 3 already has all the bits we need
Solution Implementation
1from typing import List
2from functools import reduce
3
4class Solution:
5 def countMaxOrSubsets(self, nums: List[int]) -> int:
6 """
7 Count the number of subsets with maximum bitwise OR value.
8
9 Args:
10 nums: List of integers
11
12 Returns:
13 Number of subsets whose bitwise OR equals the maximum possible OR
14 """
15
16 def dfs(index: int, current_or: int) -> None:
17 """
18 Depth-first search to explore all possible subsets.
19
20 Args:
21 index: Current position in nums array
22 current_or: Bitwise OR value accumulated so far
23 """
24 nonlocal count, max_or
25
26 # Base case: reached end of array
27 if index == len(nums):
28 # If current OR equals maximum OR, increment counter
29 if current_or == max_or:
30 count += 1
31 return
32
33 # Choice 1: Exclude current element
34 dfs(index + 1, current_or)
35
36 # Choice 2: Include current element (OR it with current value)
37 dfs(index + 1, current_or | nums[index])
38
39 # Initialize counter for subsets with maximum OR
40 count = 0
41
42 # Calculate maximum possible OR by ORing all elements
43 max_or = reduce(lambda x, y: x | y, nums)
44
45 # Start DFS from index 0 with initial OR value of 0
46 dfs(0, 0)
47
48 return count
49
1class Solution {
2 private int maxOrValue; // Maximum possible OR value of all elements
3 private int count; // Count of subsets with maximum OR value
4 private int[] numbers; // Input array reference
5
6 /**
7 * Counts the number of subsets that have the maximum bitwise OR value.
8 * The maximum OR value is obtained by OR-ing all elements in the array.
9 *
10 * @param nums The input array of integers
11 * @return The count of subsets with maximum bitwise OR value
12 */
13 public int countMaxOrSubsets(int[] nums) {
14 // Calculate the maximum possible OR value by OR-ing all elements
15 maxOrValue = 0;
16 for (int num : nums) {
17 maxOrValue |= num;
18 }
19
20 // Initialize instance variables
21 this.numbers = nums;
22 this.count = 0;
23
24 // Start DFS to explore all possible subsets
25 dfs(0, 0);
26
27 return count;
28 }
29
30 /**
31 * Depth-first search to generate all possible subsets and count those
32 * with OR value equal to the maximum.
33 *
34 * @param index Current index in the array being processed
35 * @param currentOrValue Current OR value of the subset being built
36 */
37 private void dfs(int index, int currentOrValue) {
38 // Base case: reached the end of the array
39 if (index == numbers.length) {
40 // Check if current subset's OR value equals the maximum
41 if (currentOrValue == maxOrValue) {
42 count++;
43 }
44 return;
45 }
46
47 // Branch 1: Exclude current element from subset
48 dfs(index + 1, currentOrValue);
49
50 // Branch 2: Include current element in subset
51 dfs(index + 1, currentOrValue | numbers[index]);
52 }
53}
54
1class Solution {
2public:
3 int countMaxOrSubsets(vector<int>& nums) {
4 int count = 0;
5
6 // Calculate the maximum possible OR value by OR-ing all elements
7 int maxOrValue = accumulate(nums.begin(), nums.end(), 0, bit_or<int>());
8
9 // Define a recursive DFS function to explore all subsets
10 auto dfs = [&](this auto&& dfs, int index, int currentOr) -> void {
11 // Base case: reached the end of the array
12 if (index == nums.size()) {
13 // If current subset's OR equals maximum OR, increment count
14 if (currentOr == maxOrValue) {
15 count++;
16 }
17 return;
18 }
19
20 // Choice 1: Exclude current element from subset
21 dfs(index + 1, currentOr);
22
23 // Choice 2: Include current element in subset
24 dfs(index + 1, currentOr | nums[index]);
25 };
26
27 // Start DFS from index 0 with initial OR value of 0
28 dfs(0, 0);
29
30 return count;
31 }
32};
33
1/**
2 * Counts the number of subsets whose bitwise OR equals the maximum possible OR value
3 * @param nums - Array of integers to form subsets from
4 * @returns Number of subsets with maximum OR value
5 */
6function countMaxOrSubsets(nums: number[]): number {
7 // Counter for subsets that achieve the maximum OR value
8 let answerCount: number = 0;
9
10 // Calculate the maximum possible OR value by OR-ing all elements
11 const maximumOrValue: number = nums.reduce((accumulator, currentValue) => accumulator | currentValue, 0);
12
13 /**
14 * Depth-first search to explore all possible subsets
15 * @param currentIndex - Current position in the nums array
16 * @param currentOrValue - OR value of the current subset being built
17 */
18 const depthFirstSearch = (currentIndex: number, currentOrValue: number): void => {
19 // Base case: reached the end of the array
20 if (currentIndex === nums.length) {
21 // Check if current subset's OR equals the maximum OR
22 if (currentOrValue === maximumOrValue) {
23 answerCount++;
24 }
25 return;
26 }
27
28 // Option 1: Exclude current element from subset
29 depthFirstSearch(currentIndex + 1, currentOrValue);
30
31 // Option 2: Include current element in subset
32 depthFirstSearch(currentIndex + 1, currentOrValue | nums[currentIndex]);
33 };
34
35 // Start DFS from index 0 with initial OR value of 0
36 depthFirstSearch(0, 0);
37
38 return answerCount;
39}
40
Time and Space Complexity
Time Complexity: O(2^n)
The algorithm uses a depth-first search (DFS) approach to explore all possible subsets of the array. At each index i
, the function makes two recursive calls:
- One call excludes the current element:
dfs(i + 1, t)
- One call includes the current element:
dfs(i + 1, t | nums[i])
This creates a binary decision tree where each level represents whether to include or exclude an element. With n
elements in the array, the tree has a height of n
and contains 2^n
leaf nodes, representing all possible subsets. Each subset is visited exactly once, resulting in O(2^n)
time complexity.
Space Complexity: O(n)
The space complexity is determined by the maximum depth of the recursive call stack. Since the recursion goes as deep as the length of the array (from index 0
to n
), the call stack can have at most n
recursive calls active at any given time. Each recursive call uses O(1)
space for its parameters and local variables. Therefore, the total space complexity is O(n)
.
Note that the variables ans
and mx
are shared across all recursive calls using nonlocal
and don't contribute additional space for each call.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Handle Empty Subsets
A common mistake is accidentally counting the empty subset when the initial OR value is 0. While the problem specifies "non-empty subsets", the DFS approach naturally generates an empty subset when we skip all elements.
Problematic scenario:
# If nums = [0], the maximum OR is 0 # The DFS would count both the empty subset (OR = 0) and subset [0] (OR = 0) # This gives count = 2, but the correct answer is 1 (only [0])
Solution: Add a check to ensure we're only counting non-empty subsets:
def dfs(index: int, current_or: int, selected_any: bool) -> None:
nonlocal count, max_or
if index == len(nums):
# Only count if we selected at least one element
if selected_any and current_or == max_or:
count += 1
return
# Exclude current element
dfs(index + 1, current_or, selected_any)
# Include current element (mark that we've selected something)
dfs(index + 1, current_or | nums[index], True)
# Start with selected_any = False
dfs(0, 0, False)
2. Integer Overflow in Other Languages
While Python handles arbitrary-precision integers automatically, implementing this in languages like C++ or Java could lead to overflow issues with bitwise operations on large numbers.
Solution for other languages:
Use appropriate data types (e.g., long long
in C++ or long
in Java) and be mindful of the constraints on input values.
3. Inefficient Maximum OR Calculation
Some might try to calculate the maximum OR within the DFS itself, leading to unnecessary comparisons and potential errors:
# Inefficient approach - tracking maximum during DFS
def dfs(index, current_or):
global max_or
max_or = max(max_or, current_or) # Extra work in every call
# ... rest of the logic
Solution: Pre-calculate the maximum OR as shown in the original solution. Since OR operations can only add bits (never remove them), the OR of all elements gives the maximum possible value immediately.
4. Using Global Variables Instead of Nonlocal
Using global
instead of nonlocal
or passing mutable objects can lead to issues:
# Problematic if this is inside a class method global count # This references module-level variable, not instance variable
Solution:
Use nonlocal
for nested functions or pass a mutable container:
# Option 1: nonlocal (as shown in original)
nonlocal count
# Option 2: Mutable container
result = [0] # Use list to store count
def dfs(index, current_or):
if index == len(nums):
if current_or == max_or:
result[0] += 1
# ...
5. Not Understanding Subset vs Subsequence
Some might confuse this with finding subsequences and try to maintain order, which is unnecessary for subsets.
Key distinction:
- Subset: Any selection of elements (order doesn't matter)
- Subsequence: Selection maintaining relative order
The DFS approach correctly handles subsets by considering include/exclude decisions for each index position.
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!