3022. Minimize OR of Remaining Elements Using Operations
Problem Description
You have an array of integers nums
(0-indexed) and an integer k
.
You can perform an operation where you:
- Pick any index
i
where0 <= i < nums.length - 1
- Replace
nums[i]
andnums[i + 1]
with a single value:nums[i] & nums[i + 1]
(using bitwise AND) - This reduces the array size by 1
The goal is to find the minimum possible value of the bitwise OR of all remaining elements after performing at most k operations.
For example, if you have nums = [3, 5, 7]
and perform one operation on indices 0 and 1:
3 & 5 = 1
(in binary:011 & 101 = 001
)- The array becomes
[1, 7]
- The bitwise OR of remaining elements is
1 | 7 = 7
The challenge is to strategically choose which adjacent pairs to combine (and in what order) to minimize the final OR value, while being limited to at most k
operations.
The solution uses a greedy approach with bit manipulation, checking from the most significant bit to the least significant bit to determine which bits can be eliminated from the final OR result.
Intuition
To minimize the bitwise OR of the final array, we need to minimize the number of 1-bits in the result. The key insight is that the bitwise OR will have a 1-bit at position i
if at least one element in the final array has a 1-bit at that position.
Since we can only perform AND operations (which never create new 1-bits, only preserve or eliminate existing ones), our strategy should focus on eliminating as many high-value bits as possible.
The crucial observation is that we should work greedily from the most significant bit to the least significant bit. Why? Because eliminating a higher bit has more impact on reducing the final value than eliminating a lower bit. For example, eliminating bit 29 (worth 2^29
) is more valuable than eliminating all bits from 0 to 28 combined.
For each bit position starting from the highest, we ask: "Can we eliminate this bit from the final OR result using at most k
operations?"
To check if we can eliminate a specific bit pattern (represented by a mask), we need to verify if we can merge the array elements such that none of the final elements have those bits set. We simulate this by:
- Creating a test mask that includes all bits we want to eliminate
- Counting how many "groups" we'd end up with if we performed AND operations optimally
- If the number of groups minus 1 (which equals the number of merges needed) is at most
k
, we can eliminate those bits
The counting process works by going through the array and greedily merging adjacent elements when their AND result (masked by our test pattern) becomes 0, which means those bits would be eliminated. If the AND result is non-zero, we must start a new group.
This greedy bit-by-bit approach ensures we get the minimum possible OR value by prioritizing the elimination of the most significant bits first.
Learn more about Greedy patterns.
Solution Approach
The implementation uses a greedy bit manipulation strategy, examining bits from position 29 down to 0 (covering all possible bits in a 32-bit integer).
Key Variables:
ans
: Accumulates the bits we want to try eliminating (our test mask)rans
: Accumulates the bits that must remain in the final resulttest
: The current mask being tested (ans + (1 << i)
adds the current bit to our mask)
Algorithm Steps:
-
Iterate through bits from most to least significant (29 to 0):
for i in range(29, -1, -1):
-
Create a test mask by adding the current bit to our accumulated mask:
test = ans + (1 << i)
-
Count minimum groups needed when applying the test mask:
- Initialize
cnt = 0
(group counter) andval = 0
(current group's AND accumulator) - For each number in the array:
if val == 0: val = test & num # Start new group else: val &= test & num # Continue merging with current group
- If
val
becomes non-zero after processing a number, it means we need to keep this as a separate group, so incrementcnt
- Initialize
-
Decision making:
- If
cnt > k
: We need more thank
operations to eliminate this bit pattern- Add this bit to the result:
rans += 1 << i
- Don't update
ans
(we can't eliminate this bit)
- Add this bit to the result:
- If
cnt <= k
: We can eliminate this bit pattern with at mostk
operations- Update our test mask:
ans += 1 << i
- This bit won't appear in the final result
- Update our test mask:
- If
-
Return
rans
, which contains only the bits that couldn't be eliminated.
Why this works:
- When
val & (test & num) = 0
, it means we can merge this element with the previous group and eliminate the tested bits - The number of groups minus 1 equals the number of merge operations needed
- By greedily trying to eliminate the highest bits first, we ensure the minimum possible OR value
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 = [3, 5, 7]
and k = 1
.
In binary: 3 = 011
, 5 = 101
, 7 = 111
Initial state:
ans = 0
(bits we're trying to eliminate)rans = 0
(bits that must remain in result)
Bit 2 (value 4):
- Test mask:
test = 0 + 4 = 4
(binary:100
) - Count groups needed:
- Process 3:
3 & 4 = 0
, soval = 0
(can merge) - Process 5:
5 & 4 = 4
, soval = 4
, incrementcnt = 1
- Process 7:
7 & 4 = 4
, soval = 4 & 4 = 4
(still non-zero), keepcnt = 1
- Process 3:
- Since
cnt = 1 β€ k = 1
, we can eliminate bit 2 - Update:
ans = 4
Bit 1 (value 2):
- Test mask:
test = 4 + 2 = 6
(binary:110
) - Count groups needed:
- Process 3:
3 & 6 = 2
, soval = 2
, incrementcnt = 1
- Process 5:
5 & 6 = 4
, soval = 2 & 4 = 0
(can merge) - Process 7:
7 & 6 = 6
, soval = 6
, incrementcnt = 2
- Process 3:
- Since
cnt = 2 > k = 1
, we cannot eliminate bit 1 - Update:
rans = 0 + 2 = 2
Bit 0 (value 1):
- Test mask:
test = 4 + 1 = 5
(binary:101
) - Count groups needed:
- Process 3:
3 & 5 = 1
, soval = 1
, incrementcnt = 1
- Process 5:
5 & 5 = 5
, soval = 1 & 5 = 1
(still non-zero), keepcnt = 1
- Process 7:
7 & 5 = 5
, soval = 1 & 5 = 1
(still non-zero), keepcnt = 1
- Process 3:
- Since
cnt = 1 β€ k = 1
, we can eliminate bit 0 - Update:
ans = 5
Final result: rans = 2
This means with 1 operation, we can achieve a minimum OR of 2. We'd merge indices 0 and 1 (3 & 5 = 1
), giving us [1, 7]
, and 1 | 7 = 7
. Wait, that gives 7, not 2...
Let me recalculate more carefully:
Bit 2 (value 4):
- Test mask:
test = 4
- Process elements with mask applied:
3 & 4 = 0
: Start withval = 0
(empty group)5 & 4 = 4
: Since val was 0, setval = 4
, then sinceval β 0
, incrementcnt = 1
7 & 4 = 4
:val = 4 & 4 = 4
(still non-zero)
- Final
cnt = 1
, so we need 2 groups total (1 merge operation) - Since
1 β€ k
, we can eliminate this bit pattern:ans = 4
Bit 1 (value 2):
- Test mask:
test = 4 + 2 = 6
- Process elements:
3 & 6 = 2
: Start withval = 2
, sinceval β 0
, incrementcnt = 1
5 & 6 = 4
:val = 2 & 4 = 0
(merged!)7 & 6 = 6
: Since val is 0, setval = 6
, sinceval β 0
, incrementcnt = 2
- Final
cnt = 2
means 3 groups (2 merge operations needed) - Since
2 > k
, we cannot eliminate this bit:rans = 2
Bit 0 (value 1):
- Test mask:
test = 4 + 1 = 5
- Process elements:
3 & 5 = 1
: Start withval = 1
, sinceval β 0
, incrementcnt = 1
5 & 5 = 5
:val = 1 & 5 = 1
(still non-zero)7 & 5 = 5
:val = 1 & 5 = 1
(still non-zero)
- Final
cnt = 1
, so we can eliminate this bit:ans = 5
The algorithm returns rans = 2
, indicating the minimum possible OR is 2.
To achieve this, we'd perform operations to ensure no element has bits 0 and 2 set, leaving only bit 1 possible in the final OR.
Solution Implementation
1class Solution:
2 def minOrAfterOperations(self, nums: List[int], k: int) -> int:
3 # Initialize the mask and result
4 mask = 0
5 result = 0
6
7 # Iterate through bits from most significant (29th) to least significant (0th)
8 for bit_position in range(29, -1, -1):
9 # Create a test mask by setting the current bit
10 test_mask = mask + (1 << bit_position)
11
12 # Count the number of operations needed with this test mask
13 operations_count = 0
14 current_and_value = 0
15
16 # Process each number in the array
17 for num in nums:
18 if current_and_value == 0:
19 # Start a new segment with the masked value
20 current_and_value = test_mask & num
21 else:
22 # Continue AND operation with the current segment
23 current_and_value &= test_mask & num
24
25 # If current_and_value is non-zero, we need an operation
26 if current_and_value:
27 operations_count += 1
28
29 # Check if the number of operations exceeds k
30 if operations_count > k:
31 # Cannot eliminate this bit, add it to the result
32 result += 1 << bit_position
33 else:
34 # Can eliminate this bit, update the mask for future iterations
35 mask += 1 << bit_position
36
37 return result
38
1class Solution {
2 public int minOrAfterOperations(int[] nums, int k) {
3 // mask: accumulates bits that can be zeroed out
4 // result: accumulates bits that must remain in the final OR result
5 int mask = 0, result = 0;
6
7 // Try each bit from most significant (29) to least significant (0)
8 for (int bitPosition = 29; bitPosition >= 0; bitPosition--) {
9 // Test if we can zero out this bit by including it in our mask
10 int testMask = mask | (1 << bitPosition);
11
12 // Count operations needed with this test mask
13 int operationsNeeded = 0;
14 int currentAndValue = 0;
15
16 // Simulate the operations with the test mask
17 for (int num : nums) {
18 if (currentAndValue == 0) {
19 // Start a new group
20 currentAndValue = testMask & num;
21 } else {
22 // Continue ANDing with current group
23 currentAndValue &= (testMask & num);
24 }
25
26 // If currentAndValue is not zero, we need more operations
27 if (currentAndValue != 0) {
28 operationsNeeded++;
29 }
30 }
31
32 // Check if we can afford to zero out this bit
33 if (operationsNeeded > k) {
34 // Cannot zero out this bit, it must remain in the result
35 result |= (1 << bitPosition);
36 } else {
37 // Can zero out this bit, update mask
38 mask |= (1 << bitPosition);
39 }
40 }
41
42 return result;
43 }
44}
45
1class Solution {
2public:
3 int minOrAfterOperations(vector<int>& nums, int k) {
4 // Initialize variables to track the mask and result
5 int mask = 0; // Bitmask to test which bits can be zero
6 int result = 0; // Final answer accumulating bits that must be 1
7
8 // Process bits from most significant to least significant (29 to 0)
9 for (int bitPosition = 29; bitPosition >= 0; bitPosition--) {
10 // Try to include current bit in the mask (attempt to make it zero in final OR)
11 int testMask = mask | (1 << bitPosition);
12
13 // Count how many operations are needed with this mask
14 int operationsNeeded = 0;
15 int currentAndValue = 0;
16
17 // Simulate the AND operations with the test mask
18 for (int num : nums) {
19 if (currentAndValue == 0) {
20 // Start a new segment
21 currentAndValue = testMask & num;
22 } else {
23 // Continue ANDing with current segment
24 currentAndValue &= (testMask & num);
25 }
26
27 // If currentAndValue is non-zero, we need more operations to merge
28 if (currentAndValue != 0) {
29 operationsNeeded++;
30 }
31 }
32
33 // Check if we can achieve this mask within k operations
34 if (operationsNeeded > k) {
35 // Cannot make this bit zero, so it must remain 1 in the result
36 result |= (1 << bitPosition);
37 } else {
38 // Can make this bit zero, update mask for next iterations
39 mask = testMask;
40 }
41 }
42
43 return result;
44 }
45};
46
1function minOrAfterOperations(nums: number[], k: number): number {
2 // Initialize variables to track the mask and result
3 let mask = 0; // Bitmask to test which bits can be zero
4 let result = 0; // Final answer accumulating bits that must be 1
5
6 // Process bits from most significant to least significant (29 to 0)
7 for (let bitPosition = 29; bitPosition >= 0; bitPosition--) {
8 // Try to include current bit in the mask (attempt to make it zero in final OR)
9 const testMask = mask | (1 << bitPosition);
10
11 // Count how many operations are needed with this mask
12 let operationsNeeded = 0;
13 let currentAndValue = 0;
14
15 // Simulate the AND operations with the test mask
16 for (const num of nums) {
17 if (currentAndValue === 0) {
18 // Start a new segment
19 currentAndValue = testMask & num;
20 } else {
21 // Continue ANDing with current segment
22 currentAndValue &= (testMask & num);
23 }
24
25 // If currentAndValue is non-zero, we need more operations to merge
26 if (currentAndValue !== 0) {
27 operationsNeeded++;
28 }
29 }
30
31 // Check if we can achieve this mask within k operations
32 if (operationsNeeded > k) {
33 // Cannot make this bit zero, so it must remain 1 in the result
34 result |= (1 << bitPosition);
35 } else {
36 // Can make this bit zero, update mask for next iterations
37 mask = testMask;
38 }
39 }
40
41 return result;
42}
43
Time and Space Complexity
Time Complexity: O(30 * n)
or O(n)
where n
is the length of the input array nums
.
The algorithm iterates through 30 bit positions (from bit 29 to bit 0) in the outer loop. For each bit position, it performs a complete traversal of the nums
array to count valid operations. Since the number of bits (30) is a constant, the overall time complexity simplifies to O(n)
.
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space. The variables ans
, rans
, i
, test
, cnt
, and val
are all scalar values that don't depend on the input size. No additional data structures are created that scale with the input, making the space complexity constant.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Group Counting Logic
Pitfall: A common mistake is incorrectly counting the number of groups or operations needed. Developers might think that operations_count
directly represents the number of merge operations, but it actually represents the number of groups that remain after merging.
Issue in the code:
# This counts groups, not operations! if current_and_value: operations_count += 1
Why it's confusing:
- The variable name
operations_count
is misleading - it's actually counting segments/groups - The number of operations needed is
groups - 1
, notgroups
- When we have
n
groups, we needn-1
merge operations to combine them all
Solution: Either rename the variable for clarity or adjust the counting logic:
# Option 1: Rename variable to be clearer segments_count = 0 current_and_value = 0 for num in nums: if current_and_value == 0: current_and_value = test_mask & num else: current_and_value &= test_mask & num if current_and_value: segments_count += 1 current_and_value = 0 # Reset for next segment # Check if we can merge these segments with k operations if segments_count - 1 > k: # segments - 1 = operations needed result += 1 << bit_position
2. Incorrect AND Value Reset
Pitfall: Forgetting to reset current_and_value
to 0 after counting a group, which causes incorrect group counting in subsequent iterations.
Problematic pattern:
if current_and_value: operations_count += 1 # Missing: current_and_value = 0
Why it matters: Without resetting, the algorithm continues to AND with the previous group's value, leading to incorrect results. Each group should start fresh after being counted.
Correct implementation:
for num in nums: if current_and_value == 0: current_and_value = test_mask & num else: current_and_value &= test_mask & num if current_and_value: operations_count += 1 current_and_value = 0 # Critical: Reset for next group
3. Off-by-One Error in Operation Counting
Pitfall: The code counts groups but compares directly with k
, which can lead to allowing too many operations.
The issue: If we have 3 groups, we need 2 operations to merge them into 1. But the code checks if operations_count > k
, which for k=2
would incorrectly allow 3 groups.
Fixed logic:
# Count actual groups that would remain groups_needed = 1 # Start with 1 for the final group current_and_value = 0 for num in nums: masked_value = test_mask & num if current_and_value == 0: current_and_value = masked_value else: current_and_value &= masked_value # When we can't merge anymore, we need a new group if current_and_value == 0 and masked_value != 0: groups_needed += 1 current_and_value = masked_value # Operations needed = groups - 1 operations_needed = groups_needed - 1 if operations_needed > k: result += 1 << bit_position else: mask += 1 << bit_position
These pitfalls can cause the algorithm to either incorrectly eliminate bits that shouldn't be eliminated or fail to eliminate bits that could be eliminated, resulting in a suboptimal answer.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
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
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!