feat(algorithms/daily): november 13
This commit is contained in:
parent
4b38f39928
commit
be16094742
1 changed files with 200 additions and 0 deletions
|
|
@ -38,6 +38,206 @@
|
||||||
<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/count-the-number-of-fair-pairs/"
|
||||||
|
>count the number of fair pairs</a
|
||||||
|
>
|
||||||
|
— 9/13/24
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="problem-content">
|
||||||
|
<h3>problem statement</h3>
|
||||||
|
<p>
|
||||||
|
Given an array <code>nums</code> of integers and upper/lower
|
||||||
|
integer bounds <code>upper</code>/<code>lower</code> respectively,
|
||||||
|
return the number of unique valid index pairs such that: \[i\neq
|
||||||
|
j,lower\leq nums[i]+nums[j]\leq upper\]
|
||||||
|
</p>
|
||||||
|
<h3>understanding the problem</h3>
|
||||||
|
<p>
|
||||||
|
This is another sleeper daily in which a bit of thinking in the
|
||||||
|
beginning pays dividends. Intuitively, I think it makes sense to
|
||||||
|
reduce the “dimensionality” of the problem. Choosing
|
||||||
|
both <code>i</code> and <code>j</code> concurrently seems tricky,
|
||||||
|
so let's assume we've found a valid <code>i</code>. What
|
||||||
|
must be true? Well: \[i\neq j,lower-nums[i]\leq nums[j]\leq
|
||||||
|
upper-nums[i]\]
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
It doesn't seem like we've made much progress. If nums
|
||||||
|
is a sequence of random integers,
|
||||||
|
<i
|
||||||
|
>there's truly no way to find all <code>j</code> satisfying
|
||||||
|
this condition efficiently</i
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The following question naturally arises: can we modify our input
|
||||||
|
to find such <code>j</code> efficiently? Recall our goal: find the
|
||||||
|
smallest/largest j to fit within our altered bounds—in other
|
||||||
|
words, find the smallest \(x\) less/greater than or equal to a
|
||||||
|
number. If binary search bells aren't clanging in your head
|
||||||
|
right now, I'm not sure what to say besides keep practicing.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
So, it would be nice to sort <code>nums</code> to find such
|
||||||
|
<code>j</code> relatively quickly. However:
|
||||||
|
<i>are we actually allowed to do this?</i> This is the core
|
||||||
|
question I think everyone skips over. Maybe it is trivial but it
|
||||||
|
is important to emphasize:
|
||||||
|
</p>
|
||||||
|
<ul style="list-style: none">
|
||||||
|
<li>
|
||||||
|
<i>Yes, we are allowed to sort the input</i>. Re-frame the
|
||||||
|
problem: what we are actually doing is choosing distinct
|
||||||
|
<code>i</code>, <code>j</code> to satisfy some condition. The
|
||||||
|
order of <code>nums</code> does not matter—rather, its
|
||||||
|
contents do. Any input to this algorithm with
|
||||||
|
<code>nums</code> with the same contents will yield the same
|
||||||
|
result. If we were to modify <code>nums</code> instead of
|
||||||
|
rearrange it, this would be invalid because we could be
|
||||||
|
introducing/taking away valid index combinations.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Let's consider our solution a bit more before implementing
|
||||||
|
it:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Is the approach feasible? We're sorting
|
||||||
|
<code>nums</code> then binary searching over it considering all
|
||||||
|
<code>i</code>, which will take around \(O(nlg(n))\) time.
|
||||||
|
<code>len(nums)</code>\(\leq10^5\), so this is fine.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
How do we avoid double-counting? The logic so far makes no
|
||||||
|
effort. If we consider making all pairs with indices
|
||||||
|
<i>less than</i> <code>i</code> for all
|
||||||
|
<code>i</code> left-to-right, we'll be considering all
|
||||||
|
valid pairs with no overlap. This is a common pattern—take
|
||||||
|
a moment to justify it to yourself.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<i>Exactly</i> how many elements do we count? Okay, we're
|
||||||
|
considering some rightmost index <code>i</code> and we've
|
||||||
|
found upper and lower index bounds <code>j</code> and
|
||||||
|
<code>k</code> respectively. We can pair
|
||||||
|
<code>nums[j]</code> with all elements up to an including
|
||||||
|
<code>nums[k]</code> (besides <code>nums[j]</code>). There are
|
||||||
|
exactly \(k-j\) of these. If the indexing confuses you, draw it
|
||||||
|
out and prove it to yourself.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
How do we get our final answer? Accumulate all
|
||||||
|
<code>k-j</code> for all <code>i</code>.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3>carrying out the plan</h3>
|
||||||
|
<p>
|
||||||
|
The following approach implements our logic quite elegantly and
|
||||||
|
directly. The third and fourth arguments to the
|
||||||
|
<code>bisect</code> calls specify <code>lo</code> (inclusive) and
|
||||||
|
<code>hi</code> (exclusive) bounds for our search space, mirroring
|
||||||
|
the criteria that we search across all indices \(\lt i\).
|
||||||
|
</p>
|
||||||
|
<div class="post-code">
|
||||||
|
<pre><code class="language-python">def countFairPairs(self, nums, lower, upper):
|
||||||
|
nums.sort()
|
||||||
|
ans = 0
|
||||||
|
|
||||||
|
for i, num in enumerate(nums):
|
||||||
|
k = bisect_left(nums, lower - num, 0, i)
|
||||||
|
j = bisect_right(nums, upper - num, 0, i)
|
||||||
|
|
||||||
|
ans += k - j
|
||||||
|
|
||||||
|
return ans</code></pre>
|
||||||
|
</div>
|
||||||
|
<h3>optimizing the approach</h3>
|
||||||
|
<p>
|
||||||
|
If we interpret the criteria this way, the above approach is
|
||||||
|
relatively efficient. To improve this approach, we'll need to
|
||||||
|
reinterpret the constraints. Forget about the indexing and
|
||||||
|
consider the constraint in aggregate. We want to find all \(i,j\)
|
||||||
|
with \(x=nums[i]+nums[j]\) such that \(i\neq j,lower\leq x\leq
|
||||||
|
upper\).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We <i>still</i> need to reduce the “dimensionality” of
|
||||||
|
the problem—there are just too many moving parts to consider
|
||||||
|
at once. This seems challening. Let's simplify the problem to
|
||||||
|
identify helpful ideas: pretend <code>lower</code> does not exist
|
||||||
|
(and, of course, that <code>nums</code> is sorted).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We're looking for all index pairs with sum \(\leq upper\).
|
||||||
|
And behold: (almost) two sum in the wild. This can be accomplished
|
||||||
|
with a two-pointers approach—this post is getting quite long
|
||||||
|
so we'll skip over why this is the case—but the main
|
||||||
|
win here is that we can solve this simplified version of our
|
||||||
|
problem in \(O(n)\).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Are we any closer to actually solving the problem? Now, we have
|
||||||
|
the count of index pairs \(\leq upper\). Is this our answer?
|
||||||
|
No—some may be too small, namely, with sum \(\lt lower\).
|
||||||
|
Let's exclude those by running our two-pointer approach with
|
||||||
|
and upper bound of \(lower-1\) (we want to include \(lower\)).
|
||||||
|
Now, our count reflects the total number of index pairs with a sum
|
||||||
|
in our interval bound.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Note that this really is just running a prefix sum/using the
|
||||||
|
“inclusion-exclusion” principle/however you want to
|
||||||
|
phrase it.
|
||||||
|
</p>
|
||||||
|
<div class="post-code">
|
||||||
|
<pre><code class="language-python">def countFairPairs(self, nums, lower, upper):
|
||||||
|
nums.sort()
|
||||||
|
ans = 0
|
||||||
|
|
||||||
|
def pairs_leq(x: int) -> int:
|
||||||
|
pairs = 0
|
||||||
|
l, r = 0, len(nums) - 1
|
||||||
|
while l < r:
|
||||||
|
if nums[l] + nums[r] <= x:
|
||||||
|
pairs += r - l
|
||||||
|
l += 1
|
||||||
|
else:
|
||||||
|
r -= 1
|
||||||
|
return pairs
|
||||||
|
|
||||||
|
return pairs_leq(upper) - pairs_leq(lower - 1)</code></pre>
|
||||||
|
</div>
|
||||||
|
<h3>some more considerations</h3>
|
||||||
|
<p>
|
||||||
|
The second approach is <i>asymptotically</i> equivalent. However,
|
||||||
|
it's still worth considering for two reasons:
|
||||||
|
</p>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
If an interviewer says “assume <code>nums</code> is
|
||||||
|
sorted” or “how can we do
|
||||||
|
better?”—you're cooked.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
(Much) more importantly, it's extremely valuable to be able
|
||||||
|
to <i>reconceptualize</i> a problem and look at it from
|
||||||
|
different angles. Not being locked in on a solution shows
|
||||||
|
perseverance, curiosity, and strong problem-solving abilities.
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<h3>asymptotic complexity</h3>
|
||||||
|
<p>
|
||||||
|
<u>Time Complexity</u>: \(O(nlg(n))\) for both—\(O(n)\) if
|
||||||
|
<code>nums</code> is sorted with respect to the second approach.
|
||||||
|
</p>
|
||||||
|
<p><u>Space Complexity</u>: \(\Theta(1)\) for both.</p>
|
||||||
|
</div>
|
||||||
<div class="fold">
|
<div class="fold">
|
||||||
<h2>
|
<h2>
|
||||||
<a
|
<a
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue