350 lines
20 KiB
Text
350 lines
20 KiB
Text
---
|
|
title: "leetcode daily"
|
|
date: "11/9/2024"
|
|
useKatex: true
|
|
---
|
|
|
|
# [count good numbers](https://leetcode.com/problems/count-good-numbers) <span class="date">13/12/2024</span>
|
|
|
|
## understanding the problem
|
|
|
|
This is a combinatoric problem at heart. You have some slots for evens and some for primes, with a limited number of choices for each. Leverage the multiplication rule, which states that if you have $n$ slots with $x$ choices, you get $x^n$ possible outcomes.
|
|
|
|
## doing it
|
|
|
|
So, what's the answer? If we know which slots we have and the number of choices for them, we're done. Since this is leetcode, they don't let you think—they just give you the answer. You have 2 types of slots (even and odd indices) with 5 (${0,2,4,6,8}$) and 4 (${2,3,5,7}$) choices respectively. Therefore, the answer is: $5^\text{\# even slots}\cdot 4^\text{\# odd slots}$. By counting or with small cases, we have $\lceil\frac{n}{2}\rceil$ even slots and $\lfloor\frac{n}{2}\rfloor$ odd slots. Let's submit it!
|
|
|
|
And.... TLE. Checking _everything_ when you submit your code—in this case, constraint $n\leq 10^{16}$ informs us of something suspect. In the worst case, $\frac{n}{2}\approx n^{14}$. This is far too many multiplications, so we can leverage binary exponentiation instead (and probably should've been the whole time!). Don't forget the mod.
|
|
|
|
```cpp
|
|
class Solution {
|
|
public:
|
|
static constexpr long long MOD = 1e9 + 7;
|
|
long long mpow(long long a, long long b, long long mod=MOD) {
|
|
long long ans = 1;
|
|
while (b > 0) {
|
|
if (b & 1) {
|
|
ans = (ans * a) % MOD;
|
|
}
|
|
a = (a * a) % MOD;
|
|
b >>= 1;
|
|
}
|
|
return ans;
|
|
}
|
|
int countGoodNumbers(long long n) {
|
|
long long even_slots = (n + 1) / 2, odd_slots = n / 2;
|
|
return (mpow(5, even_slots) * mpow(4, odd_slots)) % MOD;
|
|
}
|
|
};
|
|
```
|
|
|
|
# [minimum number of operations to make array distinct](https://leetcode.com/problems/minimum-number-of-operations-to-make-elements-in-array-distinc) <span class="date">04/10/2024</span>
|
|
|
|
## understanding the problem
|
|
|
|
You can remove elements in groups of 3 _solely_ from the beginning of the array. Perform this operation until there are no more duplicates left, returning the number of times you had to perform the operation.
|
|
|
|
## solution: rephrase the question
|
|
|
|
Definitionally, you remove the _last_ duplicate. If such duplicate is at 0-indexed `i`, it belongs to the $\lceil \frac{i + 1}{3}\rceil$th chunk of 3 (i.e. operation). Find the last duplicate by leveraging a frequency map and iterating backwards through the input.
|
|
|
|
## asymptotic complexity
|
|
|
|
The solution is optimal, considering the least amount of elements possible in:
|
|
|
|
- <u>Time Complexity</u>: $O(n)$
|
|
- <u>Space Complexity</u>: $\Theta(1)$
|
|
|
|
# [count the number of fair pairs](https://leetcode.com/problems/count-the-number-of-fair-pairs/) <span class="date">10/12/2024</span>
|
|
|
|
## problem statement
|
|
|
|
Given an array `nums` of integers and upper/lower integer bounds `upper`/`lower` respectively, return the number of unique valid index pairs such that:
|
|
|
|
$$i\neq j,lower\leq nums[i]+nums[j]\leq upper$$
|
|
|
|
## understanding the problem
|
|
|
|
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 `i` and `j` concurrently seems tricky, so let's assume we've found a valid `i`. What must be true? Well:
|
|
|
|
$$i\neq j,lower-nums[i]\leq nums[j]\leq upper-nums[i]$$
|
|
|
|
It doesn't seem like we've made much progress. If nums is a sequence of random integers, _there's truly no way to find all `j` satisfying this condition efficiently_.
|
|
|
|
The following question naturally arises: can we modify our input to find such `j` 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.
|
|
|
|
So, it would be nice to sort `nums` to find such `j` relatively quickly. However: _are we actually allowed to do this?_ This is the core question I think everyone skips over. Maybe it is trivial but it is important to emphasize:
|
|
|
|
- _Yes, we are allowed to sort the input_. Re-frame the problem: what we are actually doing is choosing distinct `i`, `j` to satisfy some condition. The order of `nums` does not matter—rather, its contents do. Any input to this algorithm with `nums` with the same contents will yield the same result. If we were to modify `nums` instead of rearrange it, this would be invalid because we could be introducing/taking away valid index combinations.
|
|
|
|
Let's consider our solution a bit more before implementing it:
|
|
|
|
- Is the approach feasible? We're sorting `nums` then binary searching over it considering all `i`, which will take around $O(nlg(n))$ time. `len(nums)`$\leq10^5$, so this is fine.
|
|
- How do we avoid double-counting? The logic so far makes no effort. If we consider making all pairs with indices _less than_ `i` for all `i` 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.
|
|
- _Exactly_ how many elements do we count? Okay, we're considering some rightmost index `i` and we've found upper and lower index bounds `j` and `k` respectively. We can pair `nums[j]` with all elements up to an including `nums[k]` (besides `nums[j]`). There are exactly $k-j$ of these. If the indexing confuses you, draw it out and prove it to yourself.\* How do we get our final answer? Accumulate all `k-j` for all `i`.
|
|
|
|
## carrying out the plan
|
|
|
|
The following approach implements our logic quite elegantly and directly. The third and fourth arguments to the `bisect` calls specify `lo` (inclusive) and `hi` (exclusive) bounds for our search space, mirroring the criteria that we search across all indices $\lt i$.
|
|
|
|
```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
|
|
```
|
|
|
|
## optimizing the approach
|
|
|
|
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$.
|
|
|
|
We _still_ 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 `lower` does not exist (and, of course, that `nums` is sorted).
|
|
|
|
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)$.
|
|
|
|
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.
|
|
|
|
Note that this really is just running a prefix sum/using the “inclusion-exclusion” principle/however you want to phrase it.
|
|
|
|
```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)
|
|
```
|
|
|
|
## some more considerations
|
|
|
|
The second approach is _asymptotically_ equivalent. However, it's still worth considering for two reasons:
|
|
|
|
1. If an interviewer says “assume `nums` is sorted” or “how can we do better?”—you're cooked.
|
|
2. (Much) more importantly, it's extremely valuable to be able to _reconceptualize_ a problem and look at it from different angles. Not being locked in on a solution shows perseverance, curiosity, and strong problem-solving abilities.
|
|
|
|
## asymptotic complexity
|
|
|
|
- <u>Time Complexity</u>: $O(nlg(n))$ for both—$O(n)$ if `nums` is sorted with
|
|
respect to the second approach.
|
|
- <u>Space Complexity</u>: $\Theta(1)$ for both.
|
|
|
|
# [most beautiful item for each query](https://leetcode.com/problems/most-beautiful-item-for-each-query/description/) <span class="date">09/12/2024</span>
|
|
|
|
## problem statement
|
|
|
|
Given an array `items` of $(price, beauty)$ tuples, answer each integer query of $queries$. The answer to some `query[i]` is the maximum beauty of an item with $price\leq$`items[i][0]`.
|
|
|
|
## understanding the problem
|
|
|
|
Focus on one aspect of the problem at a time. To answer a query, we need to have considered:
|
|
|
|
1. Items with a non-greater price
|
|
2. The beauty of all such items
|
|
|
|
Given some query, how can we _efficiently_ identify the “last” item with an acceptable price? Leverage the most common pre-processing algorithm: sorting. Subsequently, we can binary search `items` (keyed by price, of course) to identify all considerable items in $O(lg(n))$.
|
|
|
|
Great. Now we need to find the item with the largest beauty. Naïvely considering all the element is a _correct_ approach—but is it correct? Considering our binary search $O(lg(n))$ and beauty search $O(n)$ across $\Theta(n)$ queries with `len(items)<=len(queries)`$\leq10^5$, an $O(n^2lg(n))$ approach is certainly unacceptable.
|
|
|
|
Consider alternative approaches to responding to our queries. It is clear that answering them in-order yields no benefit (i.e. we have to consider each item all over again, per query)—could we answer them in another order to save computations?
|
|
|
|
Visualizing our items from left-to-right, we're interested in both increasing beauty and prices. If we can scan our items left to right, we can certainly “accumulate” a running maximal beauty. We can leverage sorting once again to answer our queries from left to right, then re-order them appropriately before returning a final answer. Sorting both `queries` and `items` with a linear scan will take $O(nlg(n))$ time, meeting the constraints.
|
|
|
|
## carrying out the plan
|
|
|
|
A few specifics need to be understood before coding up the approach:
|
|
|
|
- Re-ordering the queries: couple `query[i]` with `i`, then sort. When responding to queries in sorted order, we know where to place them in an output container—index `i`.
|
|
- The linear scan: accumulate a running maximal beauty, starting at index `0`. For some query `query`, we want to consider all items with price less than or equal to `query`. Therefore, loop until this condition is _violated_— the previous index will represent the last considered item.
|
|
- Edge cases: it's perfectly possible the last considered item is invalid (consider a query cheaper than the cheapest item). Return `0` as specified by the problem constraints.
|
|
|
|
```cpp
|
|
std::vector<int> maximumBeauty(std::vector<std::vector<int>>& items, std::vector<int>& queries) {
|
|
std::sort(items.begin(), items.end());
|
|
std::vector<std::pair<int, int>> sorted_queries;
|
|
sorted_queries.reserve(queries.size());
|
|
for (size_t i = 0; i < queries.size(); ++i) {
|
|
sorted_queries.emplace_back(queries[i], i);
|
|
}
|
|
std::sort(sorted_queries.begin(), sorted_queries.end());
|
|
|
|
int beauty = items[0][1];
|
|
size_t i = 0;
|
|
std::vector<int> ans(queries.size());
|
|
|
|
for (const auto [query, index] : sorted_queries) {
|
|
while (i < items.size() && items[i][0] <= query) {
|
|
beauty = std::max(beauty, items[i][1]);
|
|
++i;
|
|
}
|
|
ans[index] = i > 0 && items[i - 1][0] <= query ? beauty : 0;
|
|
}
|
|
|
|
return ans;
|
|
}
|
|
```
|
|
|
|
## asymptotic complexity
|
|
|
|
Let `n=len(items)` and `m=len(queries)`. There may be more items than queries, or vice versa. Note that a “looser” upper bound can be found by analyzing the runtime in terms of $max\{n,m\}$.
|
|
|
|
- <u>Time Complexity</u>: $O(nlg(n)+mlg(m)+m)\in O(nlg(n)+mlg(m))$. An argument
|
|
can be made that because `queries[i],items[i][{(0, 1)}]`$\leq10^9$, radix sort
|
|
can be leveraged to achieve a time complexity of $O(d \cdot (n + k + m +
|
|
k))\in O(9\cdot (n + m))\in O(n+m)$.
|
|
- <u>Space Complexity</u>: $\Theta(1)$, considering that $O(m)$ space must be
|
|
allocated. If `queries`/`items` cannot be modified in-place, increase the
|
|
space complexity by $m$/$n$ respectively.
|
|
|
|
# [shortest subarray with or at least k ii](https://leetcode.com/problems/shortest-subarray-with-or-at-least-k-ii/description/) <span class="date">09/11/2024</span>
|
|
|
|
## 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're 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's 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:
|
|
|
|
```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
|
|
```
|
|
|
|
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:
|
|
|
|
1. Below we use the [change of base formula for logarithms](https://artofproblemsolving.com/wiki/index.php/Change_of_base_formula) because $log_2(x)$ is not available in python.
|
|
2. 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.
|
|
|
|
## asymptotic complexity
|
|
|
|
Note that the size of the frequency map is bounded by $lg_{2}({10^9})\approx30$.
|
|
|
|
- <u>Space Complexity</u>: Thus, the window uses $O(1)$ space.
|
|
- <u>Time Complexity</u>: $\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.
|
|
|
|
# [minimum array end](https://leetcode.com/problems/minimum-array-end/) <span class="date">09/10/2024</span>
|
|
|
|
## problem statement
|
|
|
|
Given some $x$ and $n$, construct a strictly increasing array (say `nums` ) of length $n$ such that `nums[0] & nums[1] ... & nums[n - 1] == x` , where `&` denotes the bitwise AND operator.
|
|
|
|
Finally, return the minimum possible value of `nums[n - 1]`.
|
|
|
|
## 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$.
|
|
|
|
```cpp
|
|
long long minEnd(int n, long long x) {
|
|
int bits_to_distribute = n - 1;
|
|
long long mask = 1;
|
|
|
|
while (bits_to_distribute > 0) {
|
|
if ((x & mask) == 0) {
|
|
if ((bits_to_distribute & 1) == 1)
|
|
x |= mask;
|
|
bits_to_distribute >>= 1;
|
|
}
|
|
mask <<= 1;
|
|
}
|
|
|
|
return x;
|
|
}
|
|
```
|
|
|
|
## asymptotic complexity
|
|
|
|
- <u>Space Complexity</u>: $\Theta(1)$—a constant amount of numeric variables
|
|
are allocated regardless of $n$ and $x$.
|
|
- <u>Time Complexity</u>: 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(1)$.
|