3171. Find Subarray With Bitwise OR Closest to K
Problem Description
You are given an array nums
and an integer k
. Your task is to find a contiguous subarray where the absolute difference between k
and the bitwise OR of all elements in that subarray is minimized.
Specifically, for a subarray nums[l..r]
(where l
and r
are the left and right indices), you need to:
- Calculate the bitwise OR of all elements:
nums[l] OR nums[l+1] OR ... OR nums[r]
- Find the absolute difference:
|k - (bitwise OR result)|
- Find the subarray that gives the minimum possible absolute difference
The subarray must be non-empty and contiguous (elements must be consecutive in the original array).
For example, if you have nums = [1, 2, 3]
and k = 5
:
- Subarray
[1]
has OR = 1, difference = |5 - 1| = 4 - Subarray
[2]
has OR = 2, difference = |5 - 2| = 3 - Subarray
[3]
has OR = 3, difference = |5 - 3| = 2 - Subarray
[1, 2]
has OR = 1 OR 2 = 3, difference = |5 - 3| = 2 - Subarray
[2, 3]
has OR = 2 OR 3 = 3, difference = |5 - 3| = 2 - Subarray
[1, 2, 3]
has OR = 1 OR 2 OR 3 = 3, difference = |5 - 3| = 2
The function should return the minimum absolute difference found among all possible subarrays.
Intuition
The key insight is understanding how the bitwise OR operation behaves: once a bit is set to 1 in any element, it remains 1 in the OR result. This means as we extend a subarray to the right, the OR value can only increase or stay the same - it never decreases.
This monotonic property suggests we can use a two-pointer sliding window approach. We fix the right endpoint and adjust the left endpoint to find optimal subarrays.
Here's the thought process:
-
Expanding the window: As we add elements from the right (increasing
j
), the OR values
grows or stays constant. We check if the current OR value gives us a better answer. -
Shrinking the window: When
s > k
, we want to try reducing the OR value by removing elements from the left. But here's the challenge - removing an element doesn't simply subtract its value from the OR result. -
The bit counting trick: To properly handle element removal, we need to track how many elements contribute a 1 to each bit position. We maintain a count array
cnt[h]
that tells us how many elements in our current window have bith
set to 1. -
Removing elements: When we remove an element from the left:
- For each bit position
h
where the element has a 1, we decrementcnt[h]
- If
cnt[h]
becomes 0, it means no remaining elements in the window have that bit set, so we can turn off bith
in our OR result using XOR:s ^= 1 << h
- For each bit position
-
Why this works: By maintaining bit counts, we can efficiently update the OR value when shrinking the window. We keep shrinking while
s > k
to explore subarrays that might give us values closer tok
.
The algorithm explores all meaningful subarrays by systematically expanding and contracting the window, ensuring we find the minimum possible difference |s - k|
.
Learn more about Segment Tree and Binary Search patterns.
Solution Approach
The solution uses a two-pointer sliding window technique combined with bit manipulation to efficiently find the minimum difference.
Initial Setup:
- Calculate
m = max(nums).bit_length()
to determine the maximum number of bits needed - Create a bit count array
cnt
of sizem
to track how many elements contribute to each bit position - Initialize variables:
s = 0
(current OR value),i = 0
(left pointer),ans = inf
(minimum difference)
Main Algorithm:
-
Expand the window (right pointer
j
):for j, x in enumerate(nums): s |= x # Add current element to OR result ans = min(ans, abs(s - k)) # Update minimum difference
After adding element
x
to the OR, we update the bit counts:for h in range(m): if x >> h & 1: # Check if bit h is set in x cnt[h] += 1 # Increment count for bit h
-
Shrink the window (left pointer
i
): Whens > k
, we try to reduce the OR value by removing elements from the left:while i < j and s > k: y = nums[i] # Element to remove for h in range(m): if y >> h & 1: # If bit h is set in y cnt[h] -= 1 # Decrement count if cnt[h] == 0: # No elements have bit h set s ^= 1 << h # Turn off bit h in s i += 1 ans = min(ans, abs(s - k)) # Update after shrinking
Key Operations:
s |= x
: Adds elementx
to the OR resultx >> h & 1
: Checks if bith
is set inx
s ^= 1 << h
: Toggles (turns off) bith
ins
when no elements have it setcnt[h]
: Tracks how many elements in the current window[i, j]
have bith
set to 1
Why it works:
- The OR value only increases when expanding right, allowing us to use two pointers
- By tracking bit counts, we can correctly update the OR value when removing elements
- We explore all relevant subarrays by systematically expanding and contracting the window
- The algorithm ensures we check both scenarios: when
s ≤ k
(by expanding) and whens > k
(by shrinking)
Time Complexity: O(n × log M)
where n
is the array length and M
is the maximum value in the array
Space Complexity: O(log M)
for the bit count array
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 algorithm with nums = [3, 5, 1, 6]
and k = 4
.
Initial Setup:
- Maximum value is 6, which needs 3 bits (110 in binary)
m = 3
, so we createcnt = [0, 0, 0]
to track bits at positions 0, 1, 2s = 0
(current OR),i = 0
(left pointer),ans = inf
Step 1: j = 0, x = 3 (binary: 011)
- Add to OR:
s = 0 | 3 = 3
- Update answer:
ans = min(inf, |3 - 4|) = 1
- Update bit counts for 3:
- Bit 0: set →
cnt[0] = 1
- Bit 1: set →
cnt[1] = 1
- Bit 2: not set →
cnt[2] = 0
- Bit 0: set →
- Window: [3], OR = 3, difference = 1
Step 2: j = 1, x = 5 (binary: 101)
- Add to OR:
s = 3 | 5 = 7
(binary: 111) - Update answer:
ans = min(1, |7 - 4|) = 1
- Update bit counts for 5:
- Bit 0: set →
cnt[0] = 2
- Bit 1: not set →
cnt[1] = 1
- Bit 2: set →
cnt[2] = 1
- Bit 0: set →
- Window: [3, 5], OR = 7, difference = 3
- Since
s = 7 > k = 4
, we shrink:- Remove nums[0] = 3 from left
- Update bit counts for removing 3:
- Bit 0:
cnt[0] = 2 - 1 = 1
(still has elements with bit 0) - Bit 1:
cnt[1] = 1 - 1 = 0
(no elements have bit 1 now!)- Turn off bit 1:
s = 7 ^ 2 = 5
- Turn off bit 1:
- Bit 2: unchanged
- Bit 0:
- Move left pointer:
i = 1
- Update answer:
ans = min(1, |5 - 4|) = 1
- Window: [5], OR = 5, difference = 1
Step 3: j = 2, x = 1 (binary: 001)
- Add to OR:
s = 5 | 1 = 5
(no change, bit 0 already set) - Update answer:
ans = min(1, |5 - 4|) = 1
- Update bit counts for 1:
- Bit 0: set →
cnt[0] = 2
- Bit 1: not set →
cnt[1] = 0
- Bit 2: not set →
cnt[2] = 1
- Bit 0: set →
- Window: [5, 1], OR = 5, difference = 1
- Since
s = 5 > k = 4
, we try to shrink:- Remove nums[1] = 5 from left
- Update bit counts for removing 5:
- Bit 0:
cnt[0] = 2 - 1 = 1
(still has elements with bit 0) - Bit 2:
cnt[2] = 1 - 1 = 0
(no elements have bit 2 now!)- Turn off bit 2:
s = 5 ^ 4 = 1
- Turn off bit 2:
- Bit 0:
- Move left pointer:
i = 2
- Update answer:
ans = min(1, |1 - 4|) = 1
- Window: [1], OR = 1, difference = 3
Step 4: j = 3, x = 6 (binary: 110)
- Add to OR:
s = 1 | 6 = 7
(binary: 111) - Update answer:
ans = min(1, |7 - 4|) = 1
- Update bit counts for 6:
- Bit 0: not set →
cnt[0] = 1
- Bit 1: set →
cnt[1] = 1
- Bit 2: set →
cnt[2] = 1
- Bit 0: not set →
- Window: [1, 6], OR = 7, difference = 3
- Since
s = 7 > k = 4
, we shrink:- Remove nums[2] = 1 from left
- Update bit counts for removing 1:
- Bit 0:
cnt[0] = 1 - 1 = 0
(no elements have bit 0 now!)- Turn off bit 0:
s = 7 ^ 1 = 6
- Turn off bit 0:
- Bit 0:
- Move left pointer:
i = 3
- Update answer:
ans = min(1, |6 - 4|) = 1
- Window: [6], OR = 6, difference = 2
Final Result: The minimum difference is 1, achieved by subarrays like [3], [5], and [3,5] after shrinking.
Solution Implementation
1class Solution:
2 def minimumDifference(self, nums: List[int], k: int) -> int:
3 # Find the number of bits needed to represent the maximum number
4 num_bits = max(nums).bit_length()
5
6 # Count array to track how many numbers in current window have each bit set
7 bit_count = [0] * num_bits
8
9 # Variables for sliding window
10 current_or = 0 # Current bitwise OR of the window
11 left = 0 # Left pointer of the sliding window
12 min_difference = float('inf') # Initialize minimum difference to infinity
13
14 # Iterate through array with right pointer
15 for right, current_num in enumerate(nums):
16 # Add current number to the OR result
17 current_or |= current_num
18
19 # Update minimum difference with current window's OR
20 min_difference = min(min_difference, abs(current_or - k))
21
22 # Update bit counts for the current number
23 for bit_position in range(num_bits):
24 if current_num >> bit_position & 1:
25 bit_count[bit_position] += 1
26
27 # Shrink window from left while OR value is greater than k
28 while left < right and current_or > k:
29 left_num = nums[left]
30
31 # Update bit counts when removing left number from window
32 for bit_position in range(num_bits):
33 if left_num >> bit_position & 1:
34 bit_count[bit_position] -= 1
35 # If no numbers in window have this bit set, remove it from OR
36 if bit_count[bit_position] == 0:
37 current_or ^= 1 << bit_position
38
39 left += 1
40
41 # Update minimum difference after shrinking window
42 min_difference = min(min_difference, abs(current_or - k))
43
44 return min_difference
45
1class Solution {
2 public int minimumDifference(int[] nums, int k) {
3 // Find the maximum value to determine the number of bits needed
4 int maxValue = 0;
5 for (int num : nums) {
6 maxValue = Math.max(maxValue, num);
7 }
8
9 // Calculate the number of bits needed to represent the maximum value
10 int bitsNeeded = 32 - Integer.numberOfLeadingZeros(maxValue);
11
12 // Array to count occurrences of each bit position in the current window
13 int[] bitCount = new int[bitsNeeded];
14
15 int n = nums.length;
16 int minDifference = Integer.MAX_VALUE;
17
18 // Sliding window approach with two pointers
19 int left = 0;
20 int currentOR = 0;
21
22 for (int right = 0; right < n; right++) {
23 // Expand window by including nums[right] in the OR operation
24 currentOR |= nums[right];
25
26 // Update minimum difference with current OR value
27 minDifference = Math.min(minDifference, Math.abs(currentOR - k));
28
29 // Update bit count array for the new element
30 for (int bitPos = 0; bitPos < bitsNeeded; bitPos++) {
31 if ((nums[right] >> bitPos & 1) == 1) {
32 bitCount[bitPos]++;
33 }
34 }
35
36 // Shrink window from left while currentOR is greater than k
37 while (left < right && currentOR > k) {
38 // Process the element being removed from the window
39 for (int bitPos = 0; bitPos < bitsNeeded; bitPos++) {
40 // Check if the left element has this bit set
41 if ((nums[left] >> bitPos & 1) == 1) {
42 bitCount[bitPos]--;
43 // If no elements in window have this bit set, remove it from OR
44 if (bitCount[bitPos] == 0) {
45 currentOR ^= (1 << bitPos);
46 }
47 }
48 }
49
50 // Move left pointer forward
51 left++;
52
53 // Update minimum difference after shrinking window
54 minDifference = Math.min(minDifference, Math.abs(currentOR - k));
55 }
56 }
57
58 return minDifference;
59 }
60}
61
1class Solution {
2public:
3 int minimumDifference(vector<int>& nums, int k) {
4 // Find the maximum element to determine the number of bits needed
5 int maxElement = *max_element(nums.begin(), nums.end());
6
7 // Calculate the number of bits required to represent the maximum element
8 // 32 - __builtin_clz(x) gives the position of the highest set bit + 1
9 int numBits = 32 - __builtin_clz(maxElement);
10
11 int n = nums.size();
12 int minDifference = INT_MAX;
13
14 // Array to count how many numbers in current window have bit i set
15 vector<int> bitCount(numBits);
16
17 // Sliding window approach with two pointers
18 int left = 0;
19 int currentOrValue = 0;
20
21 for (int right = 0; right < n; ++right) {
22 // Include nums[right] in the OR operation
23 currentOrValue |= nums[right];
24
25 // Update minimum difference with current OR value
26 minDifference = min(minDifference, abs(currentOrValue - k));
27
28 // Update bit counts for the new element
29 for (int bitPos = 0; bitPos < numBits; ++bitPos) {
30 if ((nums[right] >> bitPos) & 1) {
31 ++bitCount[bitPos];
32 }
33 }
34
35 // Shrink window from left while OR value is greater than k
36 while (left < right && currentOrValue > k) {
37 // Check each bit position for the element being removed
38 for (int bitPos = 0; bitPos < numBits; ++bitPos) {
39 // If this bit is set in nums[left] and it's the last occurrence
40 if ((nums[left] >> bitPos) & 1) {
41 --bitCount[bitPos];
42 if (bitCount[bitPos] == 0) {
43 // No more elements have this bit set, remove it from OR
44 currentOrValue ^= (1 << bitPos);
45 }
46 }
47 }
48
49 // Update minimum difference after shrinking
50 minDifference = min(minDifference, abs(currentOrValue - k));
51
52 // Move left pointer forward
53 ++left;
54 }
55 }
56
57 return minDifference;
58 }
59};
60
1/**
2 * Finds the minimum absolute difference between k and the bitwise OR of any subarray
3 * @param nums - The input array of numbers
4 * @param k - The target value to compare against
5 * @returns The minimum absolute difference
6 */
7function minimumDifference(nums: number[], k: number): number {
8 // Calculate the maximum number of bits needed to represent any number in the array
9 const maxBitLength: number = Math.max(...nums).toString(2).length;
10 const arrayLength: number = nums.length;
11
12 // Array to count how many numbers in the current window have each bit set
13 const bitCount: number[] = Array(maxBitLength).fill(0);
14
15 let minDifference: number = Infinity;
16
17 // Sliding window approach with two pointers
18 let leftPointer: number = 0;
19 let currentOrValue: number = 0;
20
21 for (let rightPointer: number = 0; rightPointer < arrayLength; rightPointer++) {
22 // Add current element to the window by OR-ing it
23 currentOrValue |= nums[rightPointer];
24
25 // Update minimum difference with current OR value
26 minDifference = Math.min(minDifference, Math.abs(currentOrValue - k));
27
28 // Count the bits set in the current number
29 for (let bitPosition: number = 0; bitPosition < maxBitLength; bitPosition++) {
30 if ((nums[rightPointer] >> bitPosition) & 1) {
31 bitCount[bitPosition]++;
32 }
33 }
34
35 // Shrink window from left while OR value is greater than k
36 while (leftPointer < rightPointer && currentOrValue > k) {
37 // Update bit counts for the number being removed from window
38 for (let bitPosition: number = 0; bitPosition < maxBitLength; bitPosition++) {
39 if ((nums[leftPointer] >> bitPosition) & 1) {
40 bitCount[bitPosition]--;
41 // If no numbers in window have this bit set, remove it from OR value
42 if (bitCount[bitPosition] === 0) {
43 currentOrValue ^= (1 << bitPosition);
44 }
45 }
46 }
47
48 // Update minimum difference after shrinking window
49 minDifference = Math.min(minDifference, Math.abs(currentOrValue - k));
50 leftPointer++;
51 }
52 }
53
54 return minDifference;
55}
56
Time and Space Complexity
Time Complexity: O(n * m)
where n
is the length of the input array nums
and m
is the bit length of the maximum element in nums
.
The analysis breaks down as follows:
- The outer loop iterates through all
n
elements of the array - For each element, we perform bit operations that iterate through
m
bits (wherem = max(nums).bit_length()
) - The inner while loop with variable
i
can move from 0 ton-1
throughout the entire execution, but each element is visited at most twice (once whenj
reaches it, once wheni
leaves it) - For each movement of
i
, we again performm
bit operations - Therefore, the total operations are bounded by
O(n * m)
for the outer loop andO(n * m)
for all iterations of the inner loop combined, resulting inO(n * m)
overall
Space Complexity: O(m)
where m
is the bit length of the maximum element in nums
.
The space usage comes from:
- The
cnt
array of sizem
that tracks the count of set bits at each position - A few integer variables (
s
,i
,ans
,j
,x
,y
,h
) which takeO(1)
space - No recursive calls or additional data structures that scale with input size
- Therefore, the total space complexity is
O(m)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect OR Value Update When Removing Elements
The Pitfall: A common mistake is trying to "remove" an element from the OR result by using XOR or subtraction operations directly. For example:
# WRONG: Trying to remove element from OR current_or ^= nums[left] # This doesn't work! # or current_or -= nums[left] # This is completely wrong!
The bitwise OR operation is not reversible like addition. Once a bit is set to 1 in the OR result, you cannot simply "subtract" an element to undo its contribution. Multiple elements might contribute to the same bit being 1.
Why it fails:
- If
nums = [5, 3, 7]
where 5 = 101, 3 = 011, 7 = 111 - OR of [5, 3] = 101 | 011 = 111
- Trying to remove 5 by XOR: 111 ^ 101 = 010 (which is 2, not 3!)
- The correct answer should be 3 (just the OR of [3])
The Solution: Track how many elements contribute to each bit position. Only turn off a bit when its count reaches zero:
for bit_position in range(num_bits):
if left_num >> bit_position & 1:
bit_count[bit_position] -= 1
if bit_count[bit_position] == 0: # No elements have this bit
current_or ^= 1 << bit_position # Safe to turn off this bit
2. Not Checking Single-Element Subarrays
The Pitfall:
Starting the window shrinking only when left < right
might seem logical, but forgetting to check single-element subarrays before shrinking:
# WRONG: Missing single element check
for right, current_num in enumerate(nums):
current_or |= current_num
# Forgot to check difference here!
while left < right and current_or > k:
# shrink window...
Why it fails:
If nums = [10]
and k = 10
, the answer should be 0. But if you don't check the difference immediately after adding each element, you might miss the optimal single-element subarray.
The Solution: Always update the minimum difference immediately after expanding the window:
for right, current_num in enumerate(nums):
current_or |= current_num
min_difference = min(min_difference, abs(current_or - k)) # Check immediately!
# ... rest of the code
3. Incorrect Bit Manipulation Operations
The Pitfall: Using wrong bit manipulation operations or incorrect order of operations:
# WRONG: Using OR instead of XOR to toggle bit if bit_count[bit_position] == 0: current_or |= 1 << bit_position # This sets the bit, doesn't toggle! # WRONG: Incorrect bit checking if current_num & bit_position: # Should be (current_num >> bit_position) & 1
The Solution:
- Use XOR (
^=
) to toggle bits:current_or ^= 1 << bit_position
- Check bit correctly:
(current_num >> bit_position) & 1
- Set bit:
current_or |= 1 << bit_position
- The expression
1 << bit_position
creates a mask with only that bit set
4. Not Handling Edge Cases with Window Boundaries
The Pitfall:
The condition left < right
prevents shrinking to empty windows, but you might forget to update the answer after shrinking:
# WRONG: Only updating answer before shrinking while left < right and current_or > k: # ... shrink window logic left += 1 # Forgot to update min_difference here!
The Solution: Update the minimum difference both when expanding AND after shrinking the window to ensure all valid subarrays are considered.
Which of the following shows the order of node visit in a Breadth-first Search?
Recommended Readings
Segment Tree Faster Range Queries For this article we want to introduce the idea of a Segment Tree Segment Trees allow us to quickly perform range queries as well as range updates Suppose we have an array and we want to know the sum of a particular range of numbers as well
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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!