3171. Find Subarray With Bitwise OR Closest to K
Problem Description
You are given an array nums
and an integer k
. You need to find a subarray of nums
such that the absolute difference between k
and the bitwise OR of the subarray elements is as small as possible. In other words, select a subarray nums[l..r]
such that |k - (nums[l] OR nums[l + 1] ... OR nums[r])|
is minimum.
Return the minimum possible value of the absolute difference.
A subarray is a contiguous non-empty sequence of elements within an array.
Intuition
To solve this problem, we use the two-pointer technique combined with bitwise operations. The main idea is to leverage the properties of the bitwise OR operation, where adding more numbers to a subarray can only increase the OR value or keep it the same.
-
Bitmask Representation: Each integer in the array has a bitwise representation. We track which bits are set (have the value 1) as we iterate through
nums
. -
Fixed Right Endpoint: By fixing the right endpoint of a current subarray and trying to expand from left to right, we can efficiently calculate the OR for this segment.
-
Shrink if Necessary: If the OR value exceeds
k
, we reduce the size of the subarray from the left, maintaining an optimal solution by using a counter to see if all bits could be eliminated to explore smaller OR results. -
Optimization: The goal is to minimize
|current_OR - k|
during each iteration. The use of a counter array allows effective maintenance of currently set bits when tweaking the left endpoint.
The approach explores all potential subarrays and calculates the OR values efficiently, ensuring that whenever the current OR goes over k
, the process quickly recalibrates to find smaller, acceptable solutions.
This method efficiently utilizes bitwise operations and shifting with emphasis on maintaining a minimized computational overhead by addressing only necessary bit positions.
Learn more about Segment Tree and Binary Search patterns.
Solution Approach
The solution employs a combination of the two-pointer technique and bitwise operations to tackle the problem efficiently. Here's how the algorithm is structured:
-
Initialization:
- The variable
m
is determined by finding the bit length of the maximum number innums
. This represents the number of bits we may need to consider. - An array
cnt
of sizem
is initialized to track the count of subarray elements where each bit is set. - Other variables like
s
(current OR value),i
(left pointer), andans
(the answer) are also initialized.
- The variable
-
Iterating Over Elements:
- The
for
loop iterates withj
as the right endpoint of the subarray. s |= x
updates the current OR value by including the elementx = nums[j]
.ans
is updated withans = min(ans, abs(s - k))
to track the minimum difference.
- The
-
Adjusting the Left Endpoint:
- If the current OR
s
exceedsk
, the left pointeri
is advanced untils
is no longer greater thank
. - For each bit index
h
, if the current elementy = nums[i]
has theh
-th bit set, decrementcnt[h]
. - When
cnt[h]
is zero, it implies no remaining elements in the current subarray have theh
-th bit set, so we can safely exclude it froms
usings ^= 1 << h
. - Update
ans
after each adjustment.
- If the current OR
-
Return Result:
- After traversing the array, return
ans
, which represents the minimum absolute difference found.
- After traversing the array, return
By adjusting subarray boundaries with the two-pointer approach and carefully managing bitwise operations, the algorithm efficiently computes the required minimum difference using O(n * log M)
time complexity where n
is the number of elements and M
is the maximum element value in terms of bits.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through an example to better understand how the solution approach works.
Input Example:
nums = [1, 2, 4, 8]
k = 5
Objective:
Find a subarray whose bitwise OR is as close as possible to k
.
Step-by-step Walkthrough:
-
Initialization:
- Determine the maximum value in
nums
to calculate its bit length. Here, the max number is 8 with a bit length of 4. - Initialize
cnt
as[0, 0, 0, 0]
to keep track of set bits at each position. - Initialize
s = 0
(current OR value),i = 0
(left pointer), andans = |0 - 5| = 5
.
- Determine the maximum value in
-
Iterate Over Elements:
-
j = 0, x = nums[0] = 1:
- Compute
s |= 1
, updatings
to 1. ans
becomesmin(5, |1 - 5|) = 4
.
- Compute
-
j = 1, x = nums[1] = 2:
- Compute
s |= 2
, updatings
to 3. ans
becomesmin(4, |3 - 5|) = 2
.
- Compute
-
j = 2, x = nums[2] = 4:
- Compute
s |= 4
, updatings
to 7. ans
remainsmin(2, |7 - 5|) = 2
.
- Compute
-
-
Adjust Left Pointer When OR Exceeds
k
:- Since
s (7)
is greater thank (5)
, incrementi
untils
is manageable. - Remove nums[0] (1):
- Update
cnt
ands
accordingly. Decrementcnt
for any set bits in1
. - No change in
s
since bit positions are still covered by other elements in the subarray.
- Update
- Remove nums[1] (2):
- Update
cnt
fornums[1]
, and since all bits in2
are set, reduce current OR. Thuss
becomes 4. ans
updates tomin(2, |4 - 5|) = 1
.
- Update
- Since
-
j = 3, x = nums[3] = 8:
- Compute
s |= 8
, updatings
to 12. ans
remains1
as|k - s|
is larger.
- Compute
-
Final Adjustments:
- Adjust left pointer
i
further if necessary, to find other subarrays maintaining close OR values. - Removing elements further doesn't improve
ans
.
- Adjust left pointer
Return Result:
- The minimum absolute difference found is
1
.
This walkthrough illustrates how each element and bit operation influences the result and how the two-pointer technique helps efficiently manage subarray boundaries to optimize calculations.
Solution Implementation
1from typing import List
2from math import inf
3
4class Solution:
5 def minimumDifference(self, nums: List[int], k: int) -> int:
6 max_bits = max(nums).bit_length() # Determine the number of bits needed to represent the maximum number in nums
7 bit_count = [0] * max_bits # Array to count set bits at each position
8 current_or = 0 # Variable to store the OR of current elements
9 left = 0 # Left pointer for window
10 min_difference = inf # Initialize the minimum difference with infinity
11
12 # Iterate through each number in nums with index
13 for right, num in enumerate(nums):
14 current_or |= num # Include the current number in the OR
15 # Update the minimum difference
16 min_difference = min(min_difference, abs(current_or - k))
17
18 # Update bit count for the current number
19 for bit_position in range(max_bits):
20 if num >> bit_position & 1:
21 bit_count[bit_position] += 1
22
23 # Adjust the window size to ensure OR value does not exceed k
24 while left < right and current_or > k:
25 left_num = nums[left]
26 # Roll back the changes for the leftmost number since it's being removed from the window
27 for bit_position in range(max_bits):
28 if left_num >> bit_position & 1:
29 bit_count[bit_position] -= 1
30 if bit_count[bit_position] == 0:
31 # Remove the bit from the OR if no more numbers set it
32 current_or ^= 1 << bit_position
33 left += 1 # Move the left pointer to the right
34 # Update the minimum difference
35 min_difference = min(min_difference, abs(current_or - k))
36
37 return min_difference # Return the calculated minimum difference
38
1class Solution {
2 public int minimumDifference(int[] nums, int k) {
3 // Find the maximum value in the array to determine the number of bits needed.
4 int maxNum = 0;
5 for (int num : nums) {
6 maxNum = Math.max(maxNum, num);
7 }
8
9 // Determine the number of bits needed to represent the maximum number.
10 int numBits = 32 - Integer.numberOfLeadingZeros(maxNum);
11
12 // Array to count occurrences of bits.
13 int[] bitCount = new int[numBits];
14 int n = nums.length;
15 int minDifference = Integer.MAX_VALUE;
16
17 // Initialize pointers i, j and variable s to 0.
18 for (int i = 0, j = 0, bitwiseOrSum = 0; j < n; ++j) {
19 // Update the bitwise OR sum with the current element.
20 bitwiseOrSum |= nums[j];
21 // Update the minimum difference.
22 minDifference = Math.min(minDifference, Math.abs(bitwiseOrSum - k));
23
24 // Update the occurrences of each bit in the current number.
25 for (int bitPos = 0; bitPos < numBits; ++bitPos) {
26 if ((nums[j] >> bitPos & 1) == 1) {
27 ++bitCount[bitPos];
28 }
29 }
30
31 // Adjust the left pointer i until the condition is satisfied.
32 while (i < j && bitwiseOrSum > k) {
33 for (int bitPos = 0; bitPos < numBits; ++bitPos) {
34 if ((nums[i] >> bitPos & 1) == 1 && --bitCount[bitPos] == 0) {
35 // If the bit count is zero, remove this bit from the sum.
36 bitwiseOrSum ^= 1 << bitPos;
37 }
38 }
39 ++i; // Increment the left pointer.
40 minDifference = Math.min(minDifference, Math.abs(bitwiseOrSum - k));
41 }
42 }
43 return minDifference;
44 }
45}
46
1#include <vector>
2#include <algorithm>
3#include <climits>
4
5class Solution {
6public:
7 int minimumDifference(std::vector<int>& nums, int k) {
8 // Find the maximum value in nums
9 int maxNum = *std::max_element(nums.begin(), nums.end());
10
11 // Calculate the number of bits needed to represent maxNum
12 int bitCount = 32 - __builtin_clz(maxNum);
13
14 // Get the size of the nums vector
15 int size = nums.size();
16
17 // Initialize the answer to a large value (infinity substitute)
18 int answer = INT_MAX;
19
20 // Create a vector to count occurrences of bits
21 std::vector<int> bitCounter(bitCount);
22
23 // Initialize two pointers for the sliding window approach
24 for (int left = 0, right = 0, currentOrValue = 0; right < size; ++right) {
25 // Update the bitwise OR for the current window
26 currentOrValue |= nums[right];
27
28 // Calculate the minimum difference found so far
29 answer = std::min(answer, abs(currentOrValue - k));
30
31 // Count the bits for the current number
32 for (int bit = 0; bit < bitCount; ++bit) {
33 if (nums[right] >> bit & 1) {
34 ++bitCounter[bit];
35 }
36 }
37
38 // Adjust the window if the current OR value is greater than k
39 while (left < right && currentOrValue > k) {
40 for (int bit = 0; bit < bitCount; ++bit) {
41 if (nums[left] >> bit & 1 && --bitCounter[bit] == 0) {
42 // Remove the influence of this bit from currentOrValue
43 currentOrValue ^= 1 << bit;
44 }
45 }
46
47 // Recalculate the minimum difference after shrinking the window
48 answer = std::min(answer, abs(currentOrValue - k));
49
50 // Move the left pointer to shrink the window
51 ++left;
52 }
53 }
54 return answer;
55 }
56};
57
1function minimumDifference(nums: number[], k: number): number {
2 // Determine the binary length of the maximum number to track bit positions
3 const maxBinaryLength = Math.max(...nums).toString(2).length;
4 const n = nums.length;
5
6 // Initialize an array to count bit frequencies
7 const bitCount: number[] = Array(maxBinaryLength).fill(0);
8 let result = Infinity; // Start with the largest possible value
9
10 // Use two pointers to form a sliding window
11 for (let start = 0, end = 0, sum = 0; end < n; ++end) {
12 sum |= nums[end]; // Add current number's bits to the sum
13 result = Math.min(result, Math.abs(sum - k)); // Update result with the current difference
14
15 // Update the bit counts for current number
16 for (let bitPosition = 0; bitPosition < maxBinaryLength; ++bitPosition) {
17 if ((nums[end] >> bitPosition) & 1) {
18 ++bitCount[bitPosition];
19 }
20 }
21
22 // Slide the window to reduce sum while it exceeds k
23 while (start < end && sum > k) {
24 // Update bit counts when removing a number from the window (start side)
25 for (let bitPosition = 0; bitPosition < maxBinaryLength; ++bitPosition) {
26 if ((nums[start] >> bitPosition) & 1 && --bitCount[bitPosition] === 0) {
27 sum ^= 1 << bitPosition; // Toggle the bit if it's no longer present
28 }
29 }
30 result = Math.min(result, Math.abs(sum - k)); // Update result
31 ++start; // Move the start pointer
32 }
33 }
34
35 return result; // Return the smallest difference found
36}
37
Time and Space Complexity
The time complexity of the code is O(n * m)
, where n
is the length of the input list nums
, and m
is the maximum bit length of any element in nums
. This is because for each element in nums
, the algorithm potentially iterates over the bit length m
to update the cnt
array and recalculate s
.
The space complexity is O(m)
, as the cnt
list holds up to m
elements, which corresponds to the bit length of the maximum number in nums
.
Learn more about how to find time and space complexity quickly.
Which two pointer techniques do you use to check if a string is a palindrome?
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!