+ shortest subarray with or at least k ii + — 9/12/24 +
+problem statement
++ Given an array of non-negative integers \(num\) and some \(k\), + find the length of the shortest non-empty subarray of nums such + that its element-wise bitwise OR is greater than or equal to + \(k\)—return -1 if no such array exists. +
+developing an approach
+Another convoluted, uninspired bitwise-oriented daily.
+
+ Anways, we're looking for a subarray that satisfies a
+ condition. Considering all subarrays with
+ len(nums)\(\leq2\times10^5\) is impractical according
+ to the common rule of \(\approx10^8\) computations per second on
+ modern CPUs.
+
+ Say we's building some array xs. Adding another
+ element x to this sequence can only increase or
+ element-wise bitwise OR. Of course, it makes sense to do this.
+ However, consider xs after—it is certainly
+ possible that including x finally got us to at least
+ k. However, not all of the elements in the array are
+ useful now; we should remove some.
+
+ Which do we remove? Certainly not any from the + middle—we'd no longer be considering a subarray. We can + only remove from the beginning. +
+
+ Now, how many times do we remove? While the element-wise bitwise
+ OR of xs is \(\geq k\), we can naïvely remove
+ from the start of xs to find the smallest subarray.
+
+ Lastly, what' the state of xs after these
+ removals? Now, we (may) have an answer and the element-wise
+ bitwise OR of xs is guaranteed to be \(\lt k\).
+ Inductively, expand the array to search for a better answer.
+
+ This approach is generally called a variable-sized “sliding
+ window”. Every element of
+ nums is only added (considered in the element-wise
+ bitwise OR) or removed (discard) one time, yielding an
+ asymptotically linear time complexity. In other words, this is a
+ realistic approach for our constraints.
+
carrying out the plan
+Plugging in our algorithm to my sliding window framework:
+def minimumSubarrayLength(self, nums, k):
+ # provide a sentinel for "no window found"
+ ans = sys.maxsize
+ window = deque()
+ l = 0
+
+ # expand the window by default
+ for r in range(len(nums)):
+ # consider `nums[r]`
+ window.append(nums[r])
+ # shrink window while valid
+ while l <= r and reduce(operator.or_, window) >= k:
+ ans = min(ans, r - l + 1)
+ window.popleft()
+ l += 1
+
+ # normalize to -1 as requested
+ return -1 if ans == sys.maxsize else ans
+
+ Done, right? No. TLE.
++ If you thought this solution would work, you move too fast. + Consider every aspect of an algorithm before implementing + it. In this case, we (I) overlooked one core question: +
+-
+
- How do we maintain our element-wise bitwise OR? +
+ Calculating it by directly maintaining a window of length \(n\) + takes \(n\) time—with a maximum window size of \(n\), this + solution is \(O(n^2)\). +
++ Let's try again. Adding an element is simple—OR it to + some cumulative value. Removing an element, not so much. + Considering some \(x\) to remove, we only unset one of its bits + from our aggregated OR if it's the “last” one of + these bits set across all numbers contributing to our aggregated + value. +
++ Thus, to maintain our aggregate OR, we want to map bit + “indices” to counts. A hashmap (dictionary) or static + array will do just find. Adding/removing some \(x\) will + increment/decrement each the counter's bit count at its + respective position. I like to be uselessly specific + sometimes—choosing the latter approach, how big should our + array be? As many bits as represented by the largest of + \(nums\)—(or \(k\) itself): \[\lfloor \lg({max\{nums,k + \})}\rfloor+1\] +
+Note that:
+-
+
- + Below we use the + change of base formula for logarithms + because \(log_2(x)\) is not available in python. + +
- + It's certainly possible that \(max(nums, k)=0\). To avoid + the invalid calculation \(log(0)\), take the larger of \(1\) and + this calculation. The number of digits will then (correctly) be + \(1\) in this special case. + +
def minimumSubarrayLength(self, nums, k):
+ ans = sys.maxsize
+
+ largest = max(*nums, k)
+ num_digits = floor((log(max(largest, 1))) / log(2)) + 1
+
+ counts = [0] * num_digits
+ l = 0
+
+ def update(x, delta):
+ for i in range(len(counts)):
+ if x & 1:
+ counts[i] += 1 * delta
+ x >>= 1
+
+ def bitwise_or():
+ return reduce(
+ operator.or_,
+ (1 << i if count else 0 for i, count in enumerate(counts)),
+ 0
+ )
+
+ for r, num in enumerate(nums):
+ update(num, 1)
+ while l <= r and bitwise_or() >= k:
+ ans = min(ans, r - l + 1)
+ update(nums[l], -1)
+ l += 1
+
+ return -1 if ans == sys.maxsize else ans
+
+ asymptotic complexity
++ Note that the size of the frequency map is bounded by + \(lg_{2}({10^9})\approx30\). +
+Space Complexity:Thus, the window uses \(O(1)\) space.
+
+ Time Complexity: \(\Theta(\)len(nums)\()\)
+ —every element of nums is considered at least
+ once and takes \(O(1)\) work each to find the element-wise bitwise
+ OR.
+
understanding the problem
-- The main difficulty in this problem lies in understanding what is - being asked (intentionally or not, the phrasing is terrible). Some - initial notes: -
--
-
- The final array need not be constructed -
-
- If the element-wise bitwise AND of an array equals
-
xif and only if each element has -x's bits set—and no other bit it set by - all elements -
- -
- It makes sense to set
nums[0] == xto ensure -nums[n - 1]is minimal -
-
developing an approach
-
- An inductive approach is helpful. Consider the natural question:
- “If I had correctly generated nums[:i]”,
- how could I find nums[i]? In other words,
- how can I find the next smallest number such that
- nums
- 's element-wise bitwise AND is still \(x\)?
-
- Hmm... this is tricky. Let's think of a similar problem to - glean some insight: “Given some \(x\), how can I find the next - smallest number?”. The answer is, of course, add one (bear - with me here). -
-
- We also know that all of nums[i] must have at least
- \(x\)'s bits set. Therefore, we need to alter the unset bits of
- nums[i].
-
- The key insight of this problem is combining these two ideas to
- answer our question:
- Just “add one” to nums[i - 1]'s
- unset bits. Repeat this to find nums[n - 1].
-
- One last piece is missing—how do we know the element-wise
- bitwise AND is exactly \(x\)? Because
- nums[i > 0] only sets \(x\)'s unset bits, every
- number in nums will have at least \(x\)'s bits
- set. Further, no other bits will be set because \(x\) has them
- unset.
-
carrying out the plan
-Let's flesh out the remaining parts of the algorithm:
--
-
-
-
len(nums) == nand we initialize -nums[0] == x. So, we need to “add one” -n - 1times -
- -
- How do we carry out the additions? We could iterate \(n - 1\)
- times and simulate them. However, we already know how we want to
- alter the unset bits of
nums[0]inductively— - (add one) and how many times we want to do this (\(n - - 1\)). Because we're adding one \(n-1\) times to \(x\)'s - unset bits (right to left, of course), we simply set its unset - bits to those of \(n - 1\). -
-
- The implementation is relatively straightfoward. Traverse \(x\) from
- least-to-most significant bit, setting its \(i\)th unset bit to \(n
- - 1\)'s \(i\)th bit. Use a bitwise mask mask to
- traverse \(x\).
-
long long minEnd(int n, long long x) {
+ understanding the problem
+
+ The main difficulty in this problem lies in understanding what is
+ being asked (intentionally or not, the phrasing is terrible). Some
+ initial notes:
+
+
+ - The final array need not be constructed
+ -
+ If the element-wise bitwise AND of an array equals
+
x if and only if each element has
+ x's bits set—and no other bit it set by
+ all elements
+
+ -
+ It makes sense to set
nums[0] == x to ensure
+ nums[n - 1] is minimal
+
+
+ developing an approach
+
+ An inductive approach is helpful. Consider the natural question:
+ “If I had correctly generated nums[:i]”,
+ how could I find nums[i]? In other words,
+ how can I find the next smallest number such that
+ nums
+ 's element-wise bitwise AND is still \(x\)?
+
+
+ Hmm... this is tricky. Let's think of a similar problem to
+ glean some insight: “Given some \(x\), how can I find the
+ next smallest number?”. The answer is, of course, add one
+ (bear with me here).
+
+
+ We also know that all of nums[i] must have at least
+ \(x\)'s bits set. Therefore, we need to alter the unset bits
+ of nums[i].
+
+
+ The key insight of this problem is combining these two ideas to
+ answer our question:
+ Just “add one” to nums[i - 1]'s
+ unset bits. Repeat this to find nums[n - 1].
+
+
+ One last piece is missing—how do we know the element-wise
+ bitwise AND is exactly \(x\)? Because
+ nums[i > 0] only sets \(x\)'s unset bits, every
+ number in nums will have at least \(x\)'s bits
+ set. Further, no other bits will be set because \(x\) has them
+ unset.
+
+ carrying out the plan
+ Let's flesh out the remaining parts of the algorithm:
+
+ -
+
len(nums) == n and we initialize
+ nums[0] == x. So, we need to “add one”
+ n - 1 times
+
+ -
+ How do we carry out the additions? We could iterate \(n - 1\)
+ times and simulate them. However, we already know how we want to
+ alter the unset bits of
nums[0] inductively—
+ (add one) and how many times we want to do this (\(n -
+ 1\)). Because we're adding one \(n-1\) times to
+ \(x\)'s unset bits (right to left, of course), we simply
+ set its unset bits to those of \(n - 1\).
+
+
+
+ The implementation is relatively straightfoward. Traverse \(x\)
+ from least-to-most significant bit, setting its \(i\)th unset bit
+ to \(n - 1\)'s \(i\)th bit. Use a bitwise mask
+ mask to traverse \(x\).
+
+
+ long long minEnd(int n, long long x) {
int bits_to_distribute = n - 1;
long long mask = 1;
@@ -163,21 +342,22 @@
return x;
}
+
+ asymptotic complexity
+
+ Space Complexity: \(\Theta(1)\)—a constant amount of
+ numeric variables are allocated regardless of \(n\) and \(x\).
+
+
+ Time Complexity: in the worst case, may need to traverse
+ the entirety of \(x\) to distribute every bit of \(n - 1\) to
+ \(x\). This occurs if and only if \(x\) is all ones (\(\exists
+ k\gt 0 : 2^k-1=x\))). \(x\) and \(n\) have \(lg(x)\) and \(lg(n)\)
+ bits respectively, so the solution is \(O(lg(x) + lg(n))\in
+ O(log(xn))\). \(1\leq x,n\leq 1e8\), so this runtime is bounded by
+ \(O(log(1e8^2))\in O(log(1e16))\in O(1)\).
+
asymptotic complexity
-- Space Complexity: \(\Theta(1)\)—a constant amount of - numeric variables are allocated regardless of \(n\) and \(x\). -
-- Time Complexity: in the worst case, may need to traverse the - entirety of \(x\) to distribute every bit of \(n - 1\) to \(x\). - This occurs if and only if \(x\) is all ones (\(\exists k\gt 0 : - 2^k-1=x\))). \(x\) and \(n\) have \(lg(x)\) and \(lg(n)\) bits - respectively, so the solution is \(O(lg(x) + lg(n))\in O(log(xn))\). - \(1\leq x,n\leq 1e8\), so this runtime is bounded by - \(O(log(1e8^2))=O(log(1e16))=O(16)=O(1)\). -