feat(daily): today
This commit is contained in:
parent
ddff820d2b
commit
ffe899476d
1 changed files with 278 additions and 98 deletions
|
|
@ -38,6 +38,186 @@
|
||||||
<h1 class="post-title">Leetcode Daily</h1>
|
<h1 class="post-title">Leetcode Daily</h1>
|
||||||
</header>
|
</header>
|
||||||
<article class="post-article">
|
<article class="post-article">
|
||||||
|
<div class="fold">
|
||||||
|
<h2>
|
||||||
|
<a
|
||||||
|
target="blank"
|
||||||
|
href="https://leetcode.com/problems/shortest-subarray-with-or-at-least-k-ii/description/"
|
||||||
|
>shortest subarray with or at least k ii</a
|
||||||
|
>
|
||||||
|
— 9/12/24
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="problem-content">
|
||||||
|
<h3>problem statement</h3>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<h3>developing an approach</h3>
|
||||||
|
<p>Another convoluted, uninspired bitwise-oriented daily.</p>
|
||||||
|
<p>
|
||||||
|
Anways, we're looking for a subarray that satisfies a
|
||||||
|
condition. Considering all subarrays with
|
||||||
|
<code>len(nums)</code>\(\leq2\times10^5\) is impractical according
|
||||||
|
to the common rule of \(\approx10^8\) computations per second on
|
||||||
|
modern CPUs.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Say we's building some array <code>xs</code>. Adding another
|
||||||
|
element <code>x</code> to this sequence can only increase or
|
||||||
|
element-wise bitwise OR. Of course, it makes sense to do this.
|
||||||
|
However, consider <code>xs</code> after—it is certainly
|
||||||
|
possible that including <code>x</code> finally got us to at least
|
||||||
|
<code>k</code>. However, not all of the elements in the array are
|
||||||
|
useful now; we should remove some.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Now, how many times do we remove? While the element-wise bitwise
|
||||||
|
OR of <code>xs</code> is \(\geq k\), we can naïvely remove
|
||||||
|
from the start of <code>xs</code> to find the smallest subarray.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Lastly, what' the state of <code>xs</code> after these
|
||||||
|
removals? Now, we (may) have an answer and the element-wise
|
||||||
|
bitwise OR of <code>xs</code> is guaranteed to be \(\lt k\).
|
||||||
|
Inductively, expand the array to search for a better answer.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This approach is generally called a variable-sized “sliding
|
||||||
|
window”. Every element of
|
||||||
|
<code>nums</code> 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.
|
||||||
|
</p>
|
||||||
|
<h3>carrying out the plan</h3>
|
||||||
|
<p>Plugging in our algorithm to my sliding window framework:</p>
|
||||||
|
<div class="post-code">
|
||||||
|
<pre><code class="language-python">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
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
<p>Done, right? No. TLE.</p>
|
||||||
|
<p>
|
||||||
|
If you thought this solution would work, you move too fast.
|
||||||
|
Consider <i>every</i> aspect of an algorithm before implementing
|
||||||
|
it. In this case, we (I) overlooked one core question:
|
||||||
|
</p>
|
||||||
|
<ol style="list-style: none">
|
||||||
|
<li><i>How do we maintain our element-wise bitwise OR</i>?</li>
|
||||||
|
</ol>
|
||||||
|
<p>
|
||||||
|
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)\).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
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\]
|
||||||
|
</p>
|
||||||
|
<p>Note that:</p>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
Below we use the
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://artofproblemsolving.com/wiki/index.php/Change_of_base_formula"
|
||||||
|
>change of base formula for logarithms</a
|
||||||
|
>
|
||||||
|
because \(log_2(x)\) is not available in python.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
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.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<div class="post-code">
|
||||||
|
<pre><code class="language-python">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
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
<h3>asymptotic complexity</h3>
|
||||||
|
<p>
|
||||||
|
Note that the size of the frequency map is bounded by
|
||||||
|
\(lg_{2}({10^9})\approx30\).
|
||||||
|
</p>
|
||||||
|
<p><u>Space Complexity</u>:Thus, the window uses \(O(1)\) space.</p>
|
||||||
|
<p>
|
||||||
|
<u>Time Complexity</u>: \(\Theta(\)<code>len(nums)</code>\()\)
|
||||||
|
—every element of <code>nums</code> is considered at least
|
||||||
|
once and takes \(O(1)\) work each to find the element-wise bitwise
|
||||||
|
OR.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div class="fold">
|
<div class="fold">
|
||||||
<h2>
|
<h2>
|
||||||
<a
|
<a
|
||||||
|
|
@ -64,7 +244,6 @@
|
||||||
Finally, return the minimum possible value of
|
Finally, return the minimum possible value of
|
||||||
<code>nums[n - 1]</code>.
|
<code>nums[n - 1]</code>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
<h3>understanding the problem</h3>
|
<h3>understanding the problem</h3>
|
||||||
<p>
|
<p>
|
||||||
The main difficulty in this problem lies in understanding what is
|
The main difficulty in this problem lies in understanding what is
|
||||||
|
|
@ -97,14 +276,14 @@
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Hmm... this is tricky. Let's think of a similar problem to
|
Hmm... this is tricky. Let's think of a similar problem to
|
||||||
glean some insight: “Given some \(x\), how can I find the next
|
glean some insight: “Given some \(x\), how can I find the
|
||||||
smallest number?”. The answer is, of course, add one (bear
|
next smallest number?”. The answer is, of course, add one
|
||||||
with me here).
|
(bear with me here).
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
We also know that all of <code>nums[i]</code> must have at least
|
We also know that all of <code>nums[i]</code> must have at least
|
||||||
\(x\)'s bits set. Therefore, we need to alter the unset bits of
|
\(x\)'s bits set. Therefore, we need to alter the unset bits
|
||||||
<code>nums[i]</code>.
|
of <code>nums[i]</code>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The key insight of this problem is combining these two ideas to
|
The key insight of this problem is combining these two ideas to
|
||||||
|
|
@ -135,16 +314,16 @@
|
||||||
times and simulate them. However, we already know how we want to
|
times and simulate them. However, we already know how we want to
|
||||||
alter the unset bits of <code>nums[0]</code> inductively—
|
alter the unset bits of <code>nums[0]</code> inductively—
|
||||||
(add one) <i>and</i> how many times we want to do this (\(n -
|
(add one) <i>and</i> how many times we want to do this (\(n -
|
||||||
1\)). Because we're adding one \(n-1\) times to \(x\)'s
|
1\)). Because we're adding one \(n-1\) times to
|
||||||
unset bits (right to left, of course), we simply set its unset
|
\(x\)'s unset bits (right to left, of course), we simply
|
||||||
bits to those of \(n - 1\).
|
set its unset bits to those of \(n - 1\).
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
The implementation is relatively straightfoward. Traverse \(x\) from
|
The implementation is relatively straightfoward. Traverse \(x\)
|
||||||
least-to-most significant bit, setting its \(i\)th unset bit to \(n
|
from least-to-most significant bit, setting its \(i\)th unset bit
|
||||||
- 1\)'s \(i\)th bit. Use a bitwise mask <code>mask</code> to
|
to \(n - 1\)'s \(i\)th bit. Use a bitwise mask
|
||||||
traverse \(x\).
|
<code>mask</code> to traverse \(x\).
|
||||||
</p>
|
</p>
|
||||||
<div class="post-code">
|
<div class="post-code">
|
||||||
<pre><code class="language-cpp">long long minEnd(int n, long long x) {
|
<pre><code class="language-cpp">long long minEnd(int n, long long x) {
|
||||||
|
|
@ -170,14 +349,15 @@
|
||||||
numeric variables are allocated regardless of \(n\) and \(x\).
|
numeric variables are allocated regardless of \(n\) and \(x\).
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<u>Time Complexity</u>: in the worst case, may need to traverse the
|
<u>Time Complexity</u>: in the worst case, may need to traverse
|
||||||
entirety of \(x\) to distribute every bit of \(n - 1\) to \(x\).
|
the entirety of \(x\) to distribute every bit of \(n - 1\) to
|
||||||
This occurs if and only if \(x\) is all ones (\(\exists k\gt 0 :
|
\(x\). This occurs if and only if \(x\) is all ones (\(\exists
|
||||||
2^k-1=x\))). \(x\) and \(n\) have \(lg(x)\) and \(lg(n)\) bits
|
k\gt 0 : 2^k-1=x\))). \(x\) and \(n\) have \(lg(x)\) and \(lg(n)\)
|
||||||
respectively, so the solution is \(O(lg(x) + lg(n))\in O(log(xn))\).
|
bits respectively, so the solution is \(O(lg(x) + lg(n))\in
|
||||||
\(1\leq x,n\leq 1e8\), so this runtime is bounded by
|
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)\).
|
\(O(log(1e8^2))\in O(log(1e16))\in O(1)\).
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue