LeetCode 2680 - Maximum OR
In this problem, we are given an integer array nums and an integer k. We may perform at most k operations, where each operation selects one element and multiplies it by 2. Multiplying by 2 in binary is equivalent to shifting all bits one position to the left.
Difficulty: 🟡 Medium
Topics: Array, Greedy, Bit Manipulation, Prefix Sum
Solution
Problem Understanding
In this problem, we are given an integer array nums and an integer k. We may perform at most k operations, where each operation selects one element and multiplies it by 2.
Multiplying by 2 in binary is equivalent to shifting all bits one position to the left. For example:
5in binary is1015 * 2 = 10, which is1010
After performing up to k operations, we must compute the bitwise OR of all array elements and return the maximum possible value.
The expression:
nums[0] | nums[1] | ... | nums[n - 1]
combines all bits that are set in any element. A bit in the final result becomes 1 if at least one number contains that bit.
The key challenge is deciding where to spend the k doubling operations. We can distribute operations across multiple elements, or apply all operations to a single element.
The constraints are important:
ncan be as large as10^5nums[i]can be up to10^9kis at most15
The large array size means we need an algorithm close to linear time. A quadratic solution would be too slow.
An important observation is that left shifting one element by k positions can create very large higher bits, often contributing more to the final OR than distributing shifts across many numbers.
Several edge cases are worth considering:
- Arrays with only one element
- Very large values already containing many high bits
- Cases where shifting a smaller number creates a more useful high bit
- Situations where applying all operations to one number is optimal
- Arrays where multiple numbers already overlap heavily in binary representation
The problem guarantees:
- The array is non-empty
- Every value is positive
k >= 1
This avoids complications involving empty arrays or negative integers.
Approaches
Brute Force Approach
A brute force strategy would try every possible way to distribute the k operations among the array elements.
For example, if k = 3, we could try:
- All 3 operations on index
0 - Two on index
0, one on index1 - One on each of three indices
- Many other combinations
For each distribution, we would:
- Apply the shifts
- Compute the final OR
- Track the maximum
This approach is correct because it explores every possible valid state.
However, it is computationally infeasible. The number of ways to distribute k operations among n elements grows combinatorially. Even though k <= 15, n can reach 10^5, making exhaustive search impossible.
Key Insight
The crucial observation is that the OR operation benefits most from creating new higher-order bits.
Applying multiple left shifts to the same number creates exponentially larger values because:
x * 2^k
adds bits farther to the left.
If we split operations among multiple elements, we usually create overlapping lower bits instead of introducing entirely new higher bits.
This leads to the optimal insight:
We only need to consider applying all
koperations to exactly one element.
For every index i:
- Shift
nums[i]left byk - OR it with all other unchanged elements
- Compute the resulting value
The remaining challenge is computing the OR of all elements except index i efficiently.
We solve this using prefix OR and suffix OR arrays.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | Exponential | Exponential | Tries every distribution of operations |
| Optimal | O(n) | O(n) | Uses prefix and suffix OR arrays |
Algorithm Walkthrough
Step 1: Build a Prefix OR Array
Create an array prefix where:
prefix[i]
stores the OR of all elements from index 0 through i.
Example:
nums = [8,1,2]
prefix[0] = 8
prefix[1] = 8 | 1 = 9
prefix[2] = 9 | 2 = 11
This allows us to quickly retrieve the OR of elements before any index.
Step 2: Build a Suffix OR Array
Create another array suffix where:
suffix[i]
stores the OR of all elements from index i through n - 1.
Example:
suffix[2] = 2
suffix[1] = 1 | 2 = 3
suffix[0] = 8 | 3 = 11
This lets us efficiently retrieve the OR of elements after any index.
Step 3: Try Each Element as the Shifted Element
For each index i:
- Compute the OR of all elements before
i - Compute the OR of all elements after
i - Shift
nums[i]left byk - OR everything together
The candidate value becomes:
left_or | (nums[i] << k) | right_or
Step 4: Track the Maximum Result
Keep a running maximum across all candidates.
Since every index is evaluated independently, we eventually find the optimal answer.
Why it works
The correctness relies on the fact that left shifting increases the magnitude of bits exponentially. High-order bits dominate the OR result much more than repeatedly setting already existing lower bits.
Because OR only cares whether a bit is present at least once, concentrating all shifts on one element maximizes the chance of introducing new higher bits. Prefix and suffix OR arrays ensure we can efficiently combine the shifted element with the rest of the array.
Python Solution
from typing import List
class Solution:
def maximumOr(self, nums: List[int], k: int) -> int:
n = len(nums)
prefix = [0] * n
suffix = [0] * n
prefix[0] = nums[0]
for i in range(1, n):
prefix[i] = prefix[i - 1] | nums[i]
suffix[n - 1] = nums[n - 1]
for i in range(n - 2, -1, -1):
suffix[i] = suffix[i + 1] | nums[i]
answer = 0
for i in range(n):
left_or = prefix[i - 1] if i > 0 else 0
right_or = suffix[i + 1] if i < n - 1 else 0
shifted_value = nums[i] << k
candidate = left_or | shifted_value | right_or
answer = max(answer, candidate)
return answer
The implementation begins by constructing the prefix OR array. Each entry accumulates the OR of all previous elements including the current one.
Next, the suffix OR array is built in reverse order. This allows constant-time access to the OR of elements to the right of any position.
The main loop evaluates each index as the element receiving all k operations. The shifted value is computed using:
nums[i] << k
which is equivalent to multiplying by 2^k.
For each position, we combine:
- OR of all elements before the index
- Shifted current element
- OR of all elements after the index
The maximum candidate is returned at the end.
Go Solution
func maximumOr(nums []int, k int) int64 {
n := len(nums)
prefix := make([]int64, n)
suffix := make([]int64, n)
prefix[0] = int64(nums[0])
for i := 1; i < n; i++ {
prefix[i] = prefix[i-1] | int64(nums[i])
}
suffix[n-1] = int64(nums[n-1])
for i := n - 2; i >= 0; i-- {
suffix[i] = suffix[i+1] | int64(nums[i])
}
var answer int64 = 0
for i := 0; i < n; i++ {
var leftOr int64 = 0
var rightOr int64 = 0
if i > 0 {
leftOr = prefix[i-1]
}
if i < n-1 {
rightOr = suffix[i+1]
}
shiftedValue := int64(nums[i]) << k
candidate := leftOr | shiftedValue | rightOr
if candidate > answer {
answer = candidate
}
}
return answer
}
The Go implementation closely mirrors the Python version.
One important difference is the use of int64. Since shifting values left by up to 15 positions can exceed the range of a 32-bit integer, we explicitly use int64 to avoid overflow.
Slices are used for the prefix and suffix arrays, and bitwise operations behave similarly to Python.
Worked Examples
Example 1
nums = [12,9]
k = 1
Binary representations:
12 = 1100
9 = 1001
Prefix OR
| Index | Value | Prefix OR |
|---|---|---|
| 0 | 12 | 12 |
| 1 | 9 | 13 |
Because:
1100 | 1001 = 1101 = 13
Suffix OR
| Index | Value | Suffix OR |
|---|---|---|
| 1 | 9 | 9 |
| 0 | 12 | 13 |
Try shifting each element
| Shifted Index | Shifted Value | Result |
|---|---|---|
| 0 | 24 | 24 | 9 = 25 |
| 1 | 18 | 12 | 18 = 30 |
Maximum result:
30
Example 2
nums = [8,1,2]
k = 2
Binary:
8 = 1000
1 = 0001
2 = 0010
Prefix OR
| Index | Prefix OR |
|---|---|
| 0 | 8 |
| 1 | 9 |
| 2 | 11 |
Suffix OR
| Index | Suffix OR |
|---|---|
| 2 | 2 |
| 1 | 3 |
| 0 | 11 |
Evaluate each index
| Index | Shifted Value | Candidate |
|---|---|---|
| 0 | 32 | 32 | 1 | 2 = 35 |
| 1 | 4 | 8 | 4 | 2 = 14 |
| 2 | 8 | 8 | 1 | 8 = 9 |
Maximum:
35
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Prefix construction, suffix construction, and evaluation each scan the array once |
| Space | O(n) | Prefix and suffix arrays each require linear storage |
The algorithm performs only a constant number of passes through the array. Every OR operation is constant time because integers have bounded size. This makes the solution efficient enough for n = 10^5.
Test Cases
from typing import List
class Solution:
def maximumOr(self, nums: List[int], k: int) -> int:
n = len(nums)
prefix = [0] * n
suffix = [0] * n
prefix[0] = nums[0]
for i in range(1, n):
prefix[i] = prefix[i - 1] | nums[i]
suffix[n - 1] = nums[n - 1]
for i in range(n - 2, -1, -1):
suffix[i] = suffix[i + 1] | nums[i]
answer = 0
for i in range(n):
left_or = prefix[i - 1] if i > 0 else 0
right_or = suffix[i + 1] if i < n - 1 else 0
candidate = left_or | (nums[i] << k) | right_or
answer = max(answer, candidate)
return answer
solution = Solution()
assert solution.maximumOr([12, 9], 1) == 30 # provided example 1
assert solution.maximumOr([8, 1, 2], 2) == 35 # provided example 2
assert solution.maximumOr([1], 1) == 2 # single element
assert solution.maximumOr([1], 5) == 32 # large shift on single value
assert solution.maximumOr([1, 2, 4], 1) == 12 # shifting largest element
assert solution.maximumOr([7, 7, 7], 3) == 63 # overlapping bits
assert solution.maximumOr([1, 1, 1], 15) == 32769 # maximum shift
assert solution.maximumOr([1024, 512], 2) == 4608 # larger values
assert solution.maximumOr([3, 2, 4], 2) == 19 # middle element not optimal
assert solution.maximumOr([5, 1, 1], 3) == 41 # concentrating shifts
assert solution.maximumOr([9, 8, 3, 5], 4) == 159 # multiple candidates
Test Summary
| Test | Why |
|---|---|
[12,9], k=1 |
Validates provided example |
[8,1,2], k=2 |
Validates provided example |
[1], k=1 |
Smallest possible array |
[1], k=5 |
Large shift on single element |
[1,2,4], k=1 |
Confirms largest element may be optimal |
[7,7,7], k=3 |
Handles repeated overlapping bits |
[1,1,1], k=15 |
Stress test for maximum shift |
[1024,512], k=2 |
Tests larger binary values |
[3,2,4], k=2 |
Ensures algorithm checks every index |
[5,1,1], k=3 |
Demonstrates concentrated shifts |
[9,8,3,5], k=4 |
General multi-element scenario |
Edge Cases
One important edge case is an array containing only a single element. In this situation, there are no other elements contributing to the OR value, so the answer is simply that element shifted left by k. The implementation handles this naturally because both left_or and right_or become 0.
Another tricky case occurs when many elements already share the same set bits. For example:
[7,7,7]
Since OR ignores duplicate bits, distributing operations across multiple elements adds little value. The implementation correctly identifies that shifting one element as much as possible produces the best higher-order bits.
A third important edge case involves very large shifts. Since k can reach 15, left shifting may produce values larger than 32-bit integer limits. Python handles arbitrarily large integers automatically, but the Go solution explicitly uses int64 to prevent overflow.
A final subtle case is when the largest numerical element is not necessarily the best candidate to shift. Sometimes a smaller value produces more beneficial new bits after shifting. Because the algorithm evaluates every index independently, it never assumes the largest current value is automatically optimal.