2680. Maximum OR
Problem Description
You have an array of integers nums
(0-indexed) with length n
and an integer k
. You can perform an operation where you select any element from the array and multiply it by 2.
Your goal is to find the maximum possible value of the bitwise OR of all elements in the array (nums[0] | nums[1] | ... | nums[n - 1]
) after performing the multiplication operation at most k
times.
The bitwise OR operation (|
) combines bits from two numbers - if either bit is 1 at a position, the result has 1 at that position.
For example, if you have nums = [1, 2, 3]
and k = 2
, you could multiply one element by 2 twice (equivalent to multiplying by 4), and then calculate the OR of all elements to get your result.
The key insight is that multiplying by 2 is equivalent to a left shift operation in binary, which can significantly increase the value when combined with OR operations. You need to strategically choose which element(s) to multiply and how many times to maximize the final OR result.
Intuition
When we multiply a number by 2, we're essentially performing a left shift operation on its binary representation. For example, 5
(binary: 101
) becomes 10
(binary: 1010
). This introduces higher-order bits that weren't present before.
The key observation is that in bitwise OR operations, once a bit position is set to 1 by any number, it remains 1 in the final result. Therefore, to maximize the OR value, we want to create the highest possible bit positions with value 1.
Since multiplying by 2 shifts bits to the left, multiplying k times shifts bits k positions to the left (equivalent to multiplying by 2^k
). To maximize our result, we should apply all k operations to the same number rather than spreading them across multiple numbers. Why? Because shifting one number k positions left creates much higher bit positions than shifting multiple numbers fewer times.
Consider this: shifting 4
(binary: 100
) left by 3 positions gives 32
(binary: 100000
), while shifting three different numbers by 1 position each would only double their values, creating much smaller contributions to the final OR.
Now, which number should we choose to multiply? We need to try each number as a candidate because the optimal choice depends on both the number's value and how it interacts with other numbers through the OR operation.
For each candidate number at position i
, we need to compute:
- The OR of all numbers before position
i
(prefix OR) - The multiplied value of
nums[i]
(which isnums[i] << k
) - The OR of all numbers after position
i
(suffix OR)
By precomputing the suffix OR values and maintaining the prefix OR as we iterate, we can efficiently check each possibility and find the maximum result.
Learn more about Greedy and Prefix Sum patterns.
Solution Approach
The implementation uses a greedy approach with preprocessing to efficiently compute the maximum OR value.
Step 1: Precompute Suffix OR Array
First, we create a suffix OR array suf
where suf[i]
stores the bitwise OR of all elements from index i
to the end of the array. We build this array from right to left:
suf = [0] * (n + 1)
for i in range(n - 1, -1, -1):
suf[i] = suf[i + 1] | nums[i]
Here, suf[i] = suf[i + 1] | nums[i]
means the OR value at position i
includes the current element nums[i]
and all elements after it.
Step 2: Iterate and Calculate Maximum OR
We traverse the array from left to right while maintaining:
pre
: the prefix OR value (OR of all elements before the current position)ans
: the maximum OR value found so far
For each position i
, we calculate what the total OR would be if we applied all k
operations to nums[i]
:
ans = pre = 0
for i, x in enumerate(nums):
ans = max(ans, pre | (x << k) | suf[i + 1])
pre |= x
The expression pre | (x << k) | suf[i + 1]
computes:
pre
: OR of all elements before positioni
(x << k)
: the value ofnums[i]
after multiplying by2^k
(left shift byk
positions)suf[i + 1]
: OR of all elements after positioni
After checking position i
, we update the prefix OR by including the current element: pre |= x
.
Time and Space Complexity:
- Time:
O(n)
- we make two passes through the array - Space:
O(n)
- for storing the suffix OR array
This approach ensures we find the optimal position to apply all k
operations while efficiently computing the resulting OR value.
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 the solution with nums = [12, 9]
and k = 1
.
Step 1: Build Suffix OR Array
We build the suffix array from right to left:
suf[2] = 0
(base case, no elements)suf[1] = suf[2] | nums[1] = 0 | 9 = 9
suf[0] = suf[1] | nums[0] = 9 | 12 = 13
So suf = [13, 9, 0]
To understand why 12 | 9 = 13
, let's look at the binary:
12
in binary:1100
9
in binary:1001
- OR result:
1101
=13
Step 2: Try Each Position for Multiplication
Initialize pre = 0
and ans = 0
.
Position i = 0 (choosing to multiply 12):
- Calculate:
pre | (nums[0] << k) | suf[1]
= 0 | (12 << 1) | 9
= 0 | 24 | 9
Let's compute 24 | 9
:
24
in binary:11000
9
in binary:01001
- OR result:
11001
=25
So ans = max(0, 25) = 25
Update pre = pre | nums[0] = 0 | 12 = 12
Position i = 1 (choosing to multiply 9):
- Calculate:
pre | (nums[1] << k) | suf[2]
= 12 | (9 << 1) | 0
= 12 | 18 | 0
Let's compute 12 | 18
:
12
in binary:01100
18
in binary:10010
- OR result:
11110
=30
So ans = max(25, 30) = 30
Result: The maximum OR value is 30
, achieved by multiplying 9
by 2
to get 18
, then computing 12 | 18 = 30
.
This example demonstrates why we need to try each position: multiplying the smaller number 9
actually gives us a better result than multiplying the larger number 12
, because of how the bits align in the OR operation.
Solution Implementation
1class Solution:
2 def maximumOr(self, nums: List[int], k: int) -> int:
3 n = len(nums)
4
5 # Build suffix OR array where suffix_or[i] = nums[i] | nums[i+1] | ... | nums[n-1]
6 suffix_or = [0] * (n + 1)
7 for i in range(n - 1, -1, -1):
8 suffix_or[i] = suffix_or[i + 1] | nums[i]
9
10 max_result = 0
11 prefix_or = 0
12
13 # Try left-shifting each element and calculate the resulting OR
14 for i, num in enumerate(nums):
15 # Calculate OR with current element left-shifted by k bits
16 # prefix_or contains OR of all elements before index i
17 # suffix_or[i + 1] contains OR of all elements after index i
18 current_result = prefix_or | (num << k) | suffix_or[i + 1]
19 max_result = max(max_result, current_result)
20
21 # Update prefix OR for next iteration
22 prefix_or |= num
23
24 return max_result
25
1class Solution {
2 public long maximumOr(int[] nums, int k) {
3 int arrayLength = nums.length;
4
5 // Build suffix OR array: suffixOr[i] stores the OR of all elements from index i to end
6 long[] suffixOr = new long[arrayLength + 1];
7 for (int i = arrayLength - 1; i >= 0; i--) {
8 suffixOr[i] = suffixOr[i + 1] | nums[i];
9 }
10
11 long maxResult = 0;
12 long prefixOr = 0; // Stores the OR of all elements before current index
13
14 // Try applying all k operations to each element and find maximum OR value
15 for (int i = 0; i < arrayLength; i++) {
16 // Calculate OR when multiplying nums[i] by 2^k (left shift by k)
17 // Result = (OR of elements before i) | (nums[i] << k) | (OR of elements after i)
18 long currentOr = prefixOr | ((long) nums[i] << k) | suffixOr[i + 1];
19 maxResult = Math.max(maxResult, currentOr);
20
21 // Update prefix OR for next iteration
22 prefixOr |= nums[i];
23 }
24
25 return maxResult;
26 }
27}
28
1class Solution {
2public:
3 long long maximumOr(vector<int>& nums, int k) {
4 int n = nums.size();
5
6 // suffix_or[i] stores the OR of all elements from index i to the end
7 vector<long long> suffix_or(n + 1, 0);
8
9 // Build suffix OR array from right to left
10 for (int i = n - 1; i >= 0; --i) {
11 suffix_or[i] = suffix_or[i + 1] | nums[i];
12 }
13
14 long long max_result = 0;
15 long long prefix_or = 0;
16
17 // Try applying the k left shifts to each element
18 for (int i = 0; i < n; ++i) {
19 // Calculate OR with current element shifted left by k bits
20 // Result = (OR of elements before i) | (nums[i] << k) | (OR of elements after i)
21 long long current_result = prefix_or | (static_cast<long long>(nums[i]) << k) | suffix_or[i + 1];
22 max_result = max(max_result, current_result);
23
24 // Update prefix OR for next iteration
25 prefix_or |= nums[i];
26 }
27
28 return max_result;
29 }
30};
31
1/**
2 * Finds the maximum bitwise OR value after left-shifting exactly one element by k positions
3 * @param nums - Array of non-negative integers
4 * @param k - Number of positions to left-shift one element
5 * @returns Maximum possible bitwise OR of all elements after the operation
6 */
7function maximumOr(nums: number[], k: number): number {
8 const arrayLength: number = nums.length;
9
10 // Build suffix OR array: suffixOr[i] stores the bitwise OR of all elements from index i to end
11 const suffixOr: bigint[] = Array(arrayLength + 1).fill(0n);
12 for (let i: number = arrayLength - 1; i >= 0; i--) {
13 suffixOr[i] = suffixOr[i + 1] | BigInt(nums[i]);
14 }
15
16 // Track the maximum result and prefix OR value
17 let maxResult: number = 0;
18 let prefixOr: bigint = 0n;
19
20 // Try left-shifting each element and calculate the resulting OR
21 for (let i: number = 0; i < arrayLength; i++) {
22 // Calculate OR with current element left-shifted by k positions
23 // Formula: (prefix before i) | (nums[i] << k) | (suffix after i)
24 const currentOr: bigint = prefixOr | (BigInt(nums[i]) << BigInt(k)) | suffixOr[i + 1];
25 maxResult = Math.max(maxResult, Number(currentOr));
26
27 // Update prefix OR for next iteration
28 prefixOr |= BigInt(nums[i]);
29 }
30
31 return maxResult;
32}
33
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of two main passes through the array:
- First pass: Building the suffix OR array by iterating from index
n-1
to0
, which takesO(n)
time - Second pass: Iterating through the array once more to calculate the maximum OR value by considering each element as a candidate for left-shifting, which also takes
O(n)
time
Each operation inside the loops (bitwise OR, left shift, max comparison) takes O(1)
time. Therefore, the overall time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(n)
The algorithm uses additional space for:
- The suffix OR array
suf
of sizen + 1
, which requiresO(n)
space - A few scalar variables (
ans
,pre
,i
,x
) which requireO(1)
space
The dominant space usage comes from the suffix array, making the overall space complexity O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Distributing Operations Across Multiple Elements
The Mistake:
A common misconception is thinking that distributing the k
operations across different elements might yield a better result. For example, with k = 4
operations, one might consider applying 2 operations each to two different elements instead of all 4 to a single element.
Why It's Wrong:
The bitwise OR operation has a crucial property: once a bit position is set to 1, it remains 1 regardless of other values. Left-shifting by k
positions (multiplying by 2^k) creates higher-order bits that weren't present before. Applying all operations to one element maximizes the highest bit position we can achieve.
Example:
nums = [1, 2], k = 2 # Wrong approach: Split operations # 1 << 1 = 2, 2 << 1 = 4 # Result: 2 | 4 = 6 # Correct approach: All operations on one element # 1 << 2 = 4, keep 2 as is # Result: 4 | 2 = 6 # OR # Keep 1 as is, 2 << 2 = 8 # Result: 1 | 8 = 9 (better!)
Pitfall 2: Not Considering Elements Already Having High Bits
The Mistake: Assuming the smallest element should always receive the operations, thinking it has the most "room to grow."
Why It's Wrong: An element with existing high-order bits benefits more from left-shifting because those high bits move to even higher positions. The goal is to maximize the highest bit position in the final OR result.
Example:
nums = [1, 8], k = 1 # Wrong intuition: Shift the smaller number # 1 << 1 = 2, keep 8 # Result: 2 | 8 = 10 # Correct approach: Shift the larger number # Keep 1, 8 << 1 = 16 # Result: 1 | 16 = 17 (better!)
Pitfall 3: Attempting to Optimize Without Prefix/Suffix Arrays
The Mistake: Trying to calculate the OR result on-the-fly for each possibility without preprocessing, leading to inefficient O(n²) solutions.
Why It's Wrong: Without the prefix and suffix OR arrays, you'd need to recalculate the OR of all elements for each position you test, resulting in unnecessary repeated calculations.
Correct Implementation Reminder: The solution correctly precomputes suffix OR values and maintains prefix OR during iteration, ensuring each position is evaluated in O(1) time after the initial O(n) preprocessing.
Which data structure is used to implement priority queue?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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!