3858. Minimum Bitwise OR From Grid
Problem Description
You are given a 2D integer array grid of size m x n.
You must select exactly one integer from each row of the grid.
Return an integer denoting the minimum possible bitwise OR of the selected integers from each row.
In other words, you have a grid with m rows and n columns. From each of the m rows, you pick a single element. After picking one element per row, you compute the bitwise OR of all the chosen elements. Among all possible ways of selecting one element from each row, your task is to find the selection that produces the smallest possible bitwise OR value, and return that value.
For example, if the grid has rows [1, 6, 3], [4, 2, 9], and [7, 2, 8], you must pick exactly one number from each of the three rows, then OR them together. You want to choose the combination whose OR result is as small as possible.
Recall that the bitwise OR operation (|) sets a bit to 1 if that bit is 1 in any of the operands. Because OR can only turn bits on (never off), adding more numbers can only keep the result the same or make it larger. The challenge is to balance the choices across all rows so that the fewest high-value bits get set in the final combined result.
How We Pick the Algorithm
Why Greedy Algorithms?
This problem maps to Greedy Algorithms through a short path in the full flowchart.
Greedy bit-by-bit construction from high to low, keeping each bit off if any per-row choice allows it.
Open in FlowchartIntuition
The key observation is that the bitwise OR operation can only turn bits on, never off. This means that once a bit appears in the result, it stays there no matter what else we OR. Since higher bits contribute much larger values than lower bits (a single high bit can outweigh all the lower bits combined), our top priority should always be to avoid setting the highest possible bits first.
This naturally leads to a greedy, bit-by-bit decision from the most significant bit down to the least significant bit. For each bit position, we ask: "Is it possible to construct a valid selection (one element per row) such that this bit, together with all the higher bits we've already committed to keeping as 0, stays 0?" If yes, we greedily keep this bit at 0, because doing so reduces the answer more than anything we could save on the lower bits later. If no, we are forced to accept this bit as 1 and move on.
To check whether a bit can stay 0, we track the bits we are trying to keep off. Let ans hold the bits we've already been forced to set to 1. When testing bit i, we form a mask = ans | ((1 << i) - 1). This mask represents all the bits we are allowed to have set: the bits already in ans, plus every bit below i (since we haven't decided those yet, we let them be free). The bits we're trying to keep 0 are exactly the ones not in this mask, which includes bit i and any higher bits we previously chose to keep 0.
Now for each row, we need at least one element x that doesn't violate this requirement — meaning x uses only the allowed bits, expressed as (x | mask) == mask. If every row can supply such an element, then bit i can safely remain 0, so we leave ans unchanged. But if any row has no valid element, then no matter how we pick, that bit (or a forbidden higher bit) must turn on, so we are forced to set bit i in ans with ans |= 1 << i.
By processing bits from high to low and locking in the smallest achievable value at each step, the greedy choices combine to give the globally minimum possible bitwise OR. The reason greedy works here is the dominance of higher bits: saving one high bit is always worth more than saving any combination of lower bits, so committing to the best high-bit decision first never causes us to miss a better overall answer.
Solution Approach
The implementation uses a greedy bitwise construction pattern, building the answer one bit at a time from the most significant bit to the least significant. Below is a step-by-step walkthrough of how the code works.
Step 1: Determine how many bits we need to consider.
mx = max(map(max, grid))
m = mx.bit_length()
We first find the largest value mx across the entire grid using max(map(max, grid)). The number of bits we need to examine is mx.bit_length(), since no element in the grid can have a bit set beyond this position. This bounds the range of bit positions we iterate over.
Step 2: Initialize the answer.
ans = 0
We start with ans = 0, representing the optimistic assumption that we can keep every bit off. We will only set bits in ans when we are forced to.
Step 3: Iterate over bit positions from high to low.
for i in range(m - 1, -1, -1):
mask = ans | ((1 << i) - 1)
For each bit position i (starting from the highest), we build a mask that represents the allowed bits:
anscontributes the bits we have already been forced to set to1.(1 << i) - 1contributes all bits below positioni, which we leave free since they are decided later.
The bits not in mask are the ones we are currently trying to keep 0 — namely bit i and any higher bits we previously committed to as 0.
Step 4: Check whether each row can satisfy the mask.
for row in grid: found = False for x in row: if (x | mask) == mask: found = True break if not found: ans |= 1 << i break
For every row, we look for at least one element x that uses only the allowed bits. The condition (x | mask) == mask is true exactly when x has no bits outside of mask, meaning picking x would not turn on bit i or any forbidden higher bit. As soon as we find such an element, we mark found = True and stop scanning that row.
If any row fails to provide a valid element (found stays False), it is impossible to keep bit i off, so we set it with ans |= 1 << i and break out early — there's no need to check the remaining rows for this bit since the decision is already made.
Step 5: Return the result.
return ans
After processing all bit positions, ans holds exactly the bits that had to be 1, which is the minimum possible bitwise OR.
Complexity Analysis
- Time complexity:
O(B * m * n), whereBis the number of bits (about 30 for typical integer ranges), andm * nis the total number of grid elements. For each of theBbit positions, we may scan every element of the grid. - Space complexity:
O(1), since we only use a constant number of integer variables (mx,m,ans,mask) regardless of input size.
Data structures and patterns used:
- Bit masking is the core technique, using
maskto encode the set of allowed bits and the test(x | mask) == maskto verify feasibility. - Greedy bit-by-bit decision making drives the algorithm: by resolving higher-value bits first and locking in the best choice at each step, the locally optimal decisions combine into the globally minimum OR.
Example Walkthrough
Let's trace through the algorithm using a small grid:
grid = [[1, 6, 3], [4, 2, 9], [7, 2, 8]]
We must pick exactly one number from each row and minimize the bitwise OR of the three picks.
Step 1: Determine the number of bits.
The maximum value in the grid is 9 (max(map(max, grid))). Since 9 in binary is 1001, 9.bit_length() == 4. So we examine bit positions 3, 2, 1, 0.
For reference, here are the binary forms (4 bits):
| Value | Binary |
|---|---|
| 1 | 0001 |
| 6 | 0110 |
| 3 | 0011 |
| 4 | 0100 |
| 2 | 0010 |
| 9 | 1001 |
| 7 | 0111 |
| 8 | 1000 |
Step 2: Initialize. ans = 0 (we hope to keep every bit off).
Step 3–4: Process each bit from high (3) to low (0).
Bit i = 3 (value 8)
mask = ans | ((1 << 3) - 1) = 0 | 0111 = 0111. Allowed bits: 0,1,2. We're trying to keep bit 3 off.
Check each row for some x with (x | mask) == mask (i.e., x has no bits above bit 2):
- Row
[1, 6, 3]:1 = 0001→0001 | 0111 = 0111 = mask✓ found. - Row
[4, 2, 9]:4 = 0100→0100 | 0111 = 0111 = mask✓ found. - Row
[7, 2, 8]:7 = 0111→0111 | 0111 = 0111 = mask✓ found.
Every row can supply a value with bit 3 off. Keep bit 3 = 0. ans stays 0000.
Bit i = 2 (value 4)
mask = ans | ((1 << 2) - 1) = 0000 | 0011 = 0011. Allowed bits: 0,1. We're trying to keep bits 3 and 2 off.
Check each row for (x | 0011) == 0011 (only bits 0 and 1 allowed):
- Row
[1, 6, 3]:1 = 0001→0001 | 0011 = 0011✓ (also3 = 0011works). - Row
[4, 2, 9]:4=0100→0111✗,2=0010→0011✓ found. - Row
[7, 2, 8]:7=0111→0111✗,2=0010→0011✓ found.
Every row succeeds. Keep bit 2 = 0. ans stays 0000.
Bit i = 1 (value 2)
mask = ans | ((1 << 1) - 1) = 0000 | 0001 = 0001. Allowed bits: 0 only. We're trying to keep bits 3, 2, 1 off.
Check each row for (x | 0001) == 0001 (only bit 0 allowed):
- Row
[1, 6, 3]:1=0001→0001✓ found. - Row
[4, 2, 9]:4=0100→0101✗,2=0010→0011✗,9=1001→1001✗ → no valid element!
Row 2 cannot keep bit 1 off. So bit 1 must turn on: ans |= 1 << 1 → ans = 0010. We break early without checking row 3.
Bit i = 0 (value 1)
mask = ans | ((1 << 0) - 1) = 0010 | 0000 = 0010. Allowed bits: bit 1 (already forced) only — bit 0 must stay off.
Check each row for (x | 0010) == 0010:
- Row
[1, 6, 3]:1=0001→0011✗,6=0110→0110✗,3=0011→0011✗ → no valid element!
Row 1 cannot keep bit 0 off. So bit 0 must turn on: ans |= 1 << 0 → ans = 0011. Break early.
Step 5: Return the result.
ans = 0011 = 3.
Verification: One selection achieving OR = 3 is 3 (row 0), 2 (row 1), 2 (row 2): 3 | 2 | 2 = 0011 | 0010 | 0010 = 0011 = 3. Bits 3 and 2 were successfully kept off; bits 1 and 0 were unavoidable. The greedy choice of locking in the highest bits as 0 first yields the global minimum OR of 3.
Solution Implementation
1from typing import List
2
3
4class Solution:
5 def minimumOR(self, grid: List[List[int]]) -> int:
6 # Find the maximum value to determine how many bits we need to consider.
7 max_value = max(map(max, grid))
8 bit_count = max_value.bit_length()
9
10 # 'answer' is built greedily from the highest bit down to the lowest.
11 answer = 0
12 for bit in range(bit_count - 1, -1, -1):
13 # Tentatively assume the current bit is 0.
14 # mask = bits already fixed in 'answer' + all lower bits set to 1 (allowed to be anything).
15 mask = answer | ((1 << bit) - 1)
16
17 for row in grid:
18 # Check whether this row has an element that is a subset of 'mask'.
19 # (x | mask == mask) means every set bit of x is already in mask.
20 found = any((x | mask) == mask for x in row)
21
22 # If some row cannot satisfy the constraint with the current bit as 0,
23 # then this bit must be set to 1 in the answer.
24 if not found:
25 answer |= 1 << bit
26 break
27
28 return answer
29```
30
31**Key points / perspectives:**
32
331. **Greedy correctness:** Setting a high bit costs more than any combination of lower bits, so we always prefer keeping higher bits as `0`. We only set a bit when it's unavoidable.
34
352. **Subset check `(x | mask) == mask`:** This is equivalent to `(x & ~mask) == 0`, meaning `x` has no bits outside of `mask`. Both forms are valid; the original `|` form is kept for clarity.
36
373. **Alternative using `all()`:** You could invert the logic for the outer loop:
38 ```python3
39 if not all(any((x | mask) == mask for x in row) for row in grid):
40 answer |= 1 << bit
411class Solution {
2 public int minimumOR(int[][] grid) {
3 // Track the maximum value in the grid to know how many bits we need to inspect.
4 int maxValue = 0;
5 for (int[] row : grid) {
6 for (int value : row) {
7 maxValue = Math.max(maxValue, value);
8 }
9 }
10
11 // Number of significant bits in maxValue (the highest bit index + 1).
12 int bitCount = 32 - Integer.numberOfLeadingZeros(maxValue);
13
14 // The answer is built bit by bit, from the most significant bit downward.
15 int answer = 0;
16
17 // Greedily decide each bit: try to keep it as 0 if possible.
18 for (int bit = bitCount - 1; bit >= 0; bit--) {
19 // Candidate mask assumes the current bit is 0.
20 // It includes:
21 // - bits already fixed in 'answer' (higher bits)
22 // - all lower bits (we don't care about them yet, so allow them freely)
23 int mask = answer | ((1 << bit) - 1);
24
25 // Check every row: each row must contribute at least one element
26 // whose set bits are entirely covered by 'mask'.
27 for (int[] row : grid) {
28 boolean found = false;
29 for (int value : row) {
30 // (value | mask) == mask means 'value' has no bits outside 'mask'.
31 if ((value | mask) == mask) {
32 found = true;
33 break;
34 }
35 }
36
37 // If some row has no element that fits under the mask,
38 // we are forced to set the current bit to 1.
39 if (!found) {
40 answer |= 1 << bit;
41 break;
42 }
43 }
44 }
45
46 return answer;
47 }
48}
491class Solution {
2public:
3 int minimumOR(vector<vector<int>>& grid) {
4 // Find the maximum value across the entire grid to
5 // determine how many bits we need to consider.
6 int max_value = 0;
7 for (const auto& row : grid) {
8 max_value = max(max_value, ranges::max(row));
9 }
10
11 // Number of significant bits in max_value.
12 // (32 - count of leading zeros) gives the position of the highest set bit + 1.
13 int bit_count = 32 - __builtin_clz(max_value);
14
15 // The answer we build greedily, one bit at a time.
16 int answer = 0;
17
18 // Greedily decide each bit from the most significant down to the least.
19 // We prefer to keep a bit at 0; we only set it if it's unavoidable.
20 for (int bit = bit_count - 1; bit >= 0; --bit) {
21 // mask = bits already committed in the answer
22 // OR all lower bits ((1 << bit) - 1, which are still "free").
23 // An element "fits" if all of its set bits are within this mask.
24 int mask = answer | ((1 << bit) - 1);
25
26 // Check whether every row can supply at least one element
27 // whose set bits stay inside the current mask.
28 for (const auto& row : grid) {
29 bool found = false;
30 for (int value : row) {
31 // (value | mask) == mask means value's bits are a subset of mask.
32 if ((value | mask) == mask) {
33 found = true;
34 break;
35 }
36 }
37
38 // If some row has no fitting element, we are forced to
39 // set the current bit in the answer, then stop checking.
40 if (!found) {
41 answer |= 1 << bit;
42 break;
43 }
44 }
45 }
46
47 return answer;
48 }
49};
501function minimumOR(grid: number[][]): number {
2 // Find the maximum value in the grid to determine the highest relevant bit.
3 let maxValue = 0;
4 for (const row of grid) {
5 maxValue = Math.max(maxValue, Math.max(...row));
6 }
7
8 // Number of significant bits in maxValue.
9 // Math.clz32 counts leading zeros in a 32-bit integer.
10 const bitCount = maxValue === 0 ? 0 : 32 - Math.clz32(maxValue);
11
12 // The answer being constructed bit by bit (from high bit to low bit).
13 let answer = 0;
14
15 // Greedily decide each bit, starting from the most significant.
16 for (let bit = bitCount - 1; bit >= 0; bit--) {
17 // mask = bits already locked in the answer, plus all lower bits
18 // optimistically assumed to be allowed (set to 1).
19 // We try to keep the current `bit` as 0.
20 const mask = answer | ((1 << bit) - 1);
21
22 for (const row of grid) {
23 // Check whether this row has at least one element that fits
24 // entirely within the mask, i.e. (x | mask) === mask means
25 // x introduces no bits outside the mask.
26 let found = false;
27 for (const value of row) {
28 if ((value | mask) === mask) {
29 found = true;
30 break;
31 }
32 }
33
34 // If no element in this row fits, the current bit cannot stay 0;
35 // it must be forced to 1 in the answer.
36 if (!found) {
37 answer |= 1 << bit;
38 break;
39 }
40 }
41 }
42
43 return answer;
44}
45Time and Space Complexity
Time Complexity: O(m * n * log(mx))
Let the grid have m rows and n columns, and let mx be the maximum value in the grid.
- Computing
mx = max(map(max, grid))takesO(m * n)time, since it scans all elements. m = mx.bit_length()isO(log(mx)), which we denote asB = log(mx)bit positions.- The outer loop runs
Btimes (once for each bit position from high to low). - For each bit position, the code iterates over all rows (
mrows) and, within each row, scans up tonelements to check the condition(x | mask) == mask. - Therefore, the nested loops cost
O(B * m * n)in total.
Combining both parts, the overall time complexity is O(m * n + m * n * log(mx)) = O(m * n * log(mx)).
Space Complexity: O(1)
The algorithm only uses a constant number of auxiliary variables (mx, m, ans, mask, found, loop indices). No additional data structures that scale with the input size are allocated, so the extra space is O(1).
Common Pitfalls
Pitfall: Treating the bits as independent and locking them in too early
A very common mistake is to assume that once you decide bit i can be 0, every row will simultaneously offer a single element that keeps all the already-fixed-to-zero bits off. People often write the inner check against mask without carrying forward the bits already forced into answer, like this:
# WRONG: mask only considers the current bit, ignoring previously fixed bits.
for bit in range(bit_count - 1, -1, -1):
mask = (1 << bit) - 1 # <-- missing 'answer |'
for row in grid:
found = any((x | mask) == mask for x in row)
if not found:
answer |= 1 << bit
break
Why this is wrong: The greedy decision for bit i must respect all earlier commitments. When you successfully kept bits k > i at 0, you implicitly promised that the same single pick per row avoids those higher bits too. If mask drops the answer term, you allow a row to choose an element that satisfies bit i but turns on a higher bit you already committed to keeping 0. The bits are not independent because you pick one element per row, and that element's OR contribution affects every bit at once.
Concrete failure example:
grid = [[7, 4], [6, 1]] # Binary: # 7 = 111, 4 = 100 # 6 = 110, 1 = 001
With the buggy mask = (1 << bit) - 1:
- Bit 2: row0 has
4=100(fails,100|011 != 011), so bit 2 is forced →answer = 100. Correct so far. - Bit 1: buggy
mask = 011(ignoresanswer). Row0:4|011=111 != 011,7|011=111 != 011→ fails → bit 1 set. - This may diverge from the truth because the check no longer verifies that the same element keeping bit 1 off also keeps the committed bit 2 off.
The correct version keeps mask = answer | ((1 << bit) - 1), so each row must find one element whose set bits are a subset of the allowed region (previously-fixed bits + all lower free bits). This guarantees a consistent, simultaneously-valid choice across all bits.
Solution
Always fold the accumulated answer into the mask so the feasibility test honors every prior commitment:
class Solution:
def minimumOR(self, grid: List[List[int]]) -> int:
max_value = max(map(max, grid))
bit_count = max_value.bit_length()
answer = 0
for bit in range(bit_count - 1, -1, -1):
# Carry forward fixed bits in 'answer'; allow all lower bits freely.
mask = answer | ((1 << bit) - 1)
for row in grid:
# Each row needs ONE element whose bits are a subset of 'mask'.
if not any((x | mask) == mask for x in row):
answer |= 1 << bit
break
return answer
Secondary pitfall: Off-by-one in the bit range
Using range(bit_count, -1, -1) (looping one extra high bit) wastes work but is harmless, while using mx.bit_length() correctly gives the highest meaningful bit index as bit_count - 1. A more dangerous variant is hardcoding a fixed width like range(30, -1, -1) — fine for constrained inputs, but it silently breaks if values exceed 2^30. Prefer max_value.bit_length() so the range adapts to the actual data and you never miss a high bit that some element sets.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhich of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?
Recommended Readings
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 him
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity describes how the time needed
Want a Structured Path to Master System Design Too? Don’t Miss This!