Facebook Pixel

3022. Minimize OR of Remaining Elements Using Operations

HardGreedyBit ManipulationArray
Leetcode Link

Problem Description

You have an array of integers nums (0-indexed) and an integer k.

You can perform an operation where you:

  1. Pick any index i where 0 <= i < nums.length - 1
  2. Replace nums[i] and nums[i + 1] with a single value: nums[i] & nums[i + 1] (using bitwise AND)
  3. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Creating a test mask that includes all bits we want to eliminate
  2. Counting how many "groups" we'd end up with if we performed AND operations optimally
  3. 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 result
  • test: The current mask being tested (ans + (1 << i) adds the current bit to our mask)

Algorithm Steps:

  1. Iterate through bits from most to least significant (29 to 0):

    for i in range(29, -1, -1):
  2. Create a test mask by adding the current bit to our accumulated mask:

    test = ans + (1 << i)
  3. Count minimum groups needed when applying the test mask:

    • Initialize cnt = 0 (group counter) and val = 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 increment cnt
  4. Decision making:

    • If cnt > k: We need more than k 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)
    • If cnt <= k: We can eliminate this bit pattern with at most k operations
      • Update our test mask: ans += 1 << i
      • This bit won't appear in the final result
  5. 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 Evaluator

Example 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, so val = 0 (can merge)
    • Process 5: 5 & 4 = 4, so val = 4, increment cnt = 1
    • Process 7: 7 & 4 = 4, so val = 4 & 4 = 4 (still non-zero), keep cnt = 1
  • 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, so val = 2, increment cnt = 1
    • Process 5: 5 & 6 = 4, so val = 2 & 4 = 0 (can merge)
    • Process 7: 7 & 6 = 6, so val = 6, increment cnt = 2
  • 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, so val = 1, increment cnt = 1
    • Process 5: 5 & 5 = 5, so val = 1 & 5 = 1 (still non-zero), keep cnt = 1
    • Process 7: 7 & 5 = 5, so val = 1 & 5 = 1 (still non-zero), keep cnt = 1
  • 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 with val = 0 (empty group)
    • 5 & 4 = 4: Since val was 0, set val = 4, then since val β‰  0, increment cnt = 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 with val = 2, since val β‰  0, increment cnt = 1
    • 5 & 6 = 4: val = 2 & 4 = 0 (merged!)
    • 7 & 6 = 6: Since val is 0, set val = 6, since val β‰  0, increment cnt = 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 with val = 1, since val β‰  0, increment cnt = 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, not groups
  • When we have n groups, we need n-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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm is best for finding the shortest distance between two points in an unweighted graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More