From 2bcc3f467577152369755a9326163b67bf386a65 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Fri, 29 Nov 2024 22:13:16 -0600 Subject: [PATCH] fix(posts): fetch daily from files --- posts/algorithms/leetcode-daily.html | 570 ++++++++++++++++++ .../.null-ls_814785_cfps-twoptr.py | 1 + .../code/algorithms/leetcode-daily/beauty.cpp | 28 + .../algorithms/leetcode-daily/cfps-naive.py | 11 + .../algorithms/leetcode-daily/cfps-twoptr.py | 16 + .../code/algorithms/leetcode-daily/minend.cpp | 16 + .../algorithms/leetcode-daily/msl-bitwise.py | 30 + .../algorithms/leetcode-daily/msl-naive.py | 18 + scripts/index.js | 1 + 9 files changed, 691 insertions(+) create mode 100644 posts/algorithms/leetcode-daily.html create mode 100644 public/code/algorithms/leetcode-daily/.null-ls_814785_cfps-twoptr.py create mode 100644 public/code/algorithms/leetcode-daily/beauty.cpp create mode 100644 public/code/algorithms/leetcode-daily/cfps-naive.py create mode 100644 public/code/algorithms/leetcode-daily/cfps-twoptr.py create mode 100644 public/code/algorithms/leetcode-daily/minend.cpp create mode 100644 public/code/algorithms/leetcode-daily/msl-bitwise.py create mode 100644 public/code/algorithms/leetcode-daily/msl-naive.py diff --git a/posts/algorithms/leetcode-daily.html b/posts/algorithms/leetcode-daily.html new file mode 100644 index 0000000..ff47c2b --- /dev/null +++ b/posts/algorithms/leetcode-daily.html @@ -0,0 +1,570 @@ + + + + + + + + + + + + + + Barrett Ruth + + +
+ +
+ barrett@ruth:~$ /algorithms + +
+
+
+
+
+
+

Leetcode Daily

+
+
+

+ count the number of fair pairs + — 9/13/24 +

+
+

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\). +

+
+

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. +

+
+

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. +
  3. + (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. +
  4. +
+

asymptotic complexity

+

+ Time Complexity: \(O(nlg(n))\) for both—\(O(n)\) if + nums is sorted with respect to the second approach. +

+

Space Complexity: \(\Theta(1)\) for both.

+
+

+ most beautiful item for each query + — 9/12/24 +

+
+

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. +
  3. The beauty of all such items
  4. +
+

+ 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's 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 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. +
  • +
+
+

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\}\). +

+

+ Time Complexity: \(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)\). +

+

+ Space Complexity: \(\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 + — 9/11/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:

+
+

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: +

+
    +
  1. How do we maintain our element-wise bitwise OR?
  2. +
+

+ 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 + because \(log_2(x)\) is not available in python. +
  2. +
  3. + 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. +
  4. +
+
+

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. +

+
+

+ minimum array end + — 9/10/24 +

+
+

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\). +

+
+

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(1)\). +

+
+
+
+
+ + + + diff --git a/public/code/algorithms/leetcode-daily/.null-ls_814785_cfps-twoptr.py b/public/code/algorithms/leetcode-daily/.null-ls_814785_cfps-twoptr.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/public/code/algorithms/leetcode-daily/.null-ls_814785_cfps-twoptr.py @@ -0,0 +1 @@ + diff --git a/public/code/algorithms/leetcode-daily/beauty.cpp b/public/code/algorithms/leetcode-daily/beauty.cpp new file mode 100644 index 0000000..306aae0 --- /dev/null +++ b/public/code/algorithms/leetcode-daily/beauty.cpp @@ -0,0 +1,28 @@ +#include +#include + +std::vector maximumBeauty(std::vector>& items, std::vector& queries) { + std::sort(items.begin(), items.end()); + std::vector> sorted_queries; + sorted_queries.reserve(queries.size()); + // couple queries with their indices + 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 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; + } + // invariant: items[i - 1] is the rightmost considerable item + ans[index] = i > 0 && items[i - 1][0] <= query ? beauty : 0; + } + + return std::move(ans); +} diff --git a/public/code/algorithms/leetcode-daily/cfps-naive.py b/public/code/algorithms/leetcode-daily/cfps-naive.py new file mode 100644 index 0000000..1e882d8 --- /dev/null +++ b/public/code/algorithms/leetcode-daily/cfps-naive.py @@ -0,0 +1,11 @@ +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 diff --git a/public/code/algorithms/leetcode-daily/cfps-twoptr.py b/public/code/algorithms/leetcode-daily/cfps-twoptr.py new file mode 100644 index 0000000..0636fa9 --- /dev/null +++ b/public/code/algorithms/leetcode-daily/cfps-twoptr.py @@ -0,0 +1,16 @@ +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) diff --git a/public/code/algorithms/leetcode-daily/minend.cpp b/public/code/algorithms/leetcode-daily/minend.cpp new file mode 100644 index 0000000..40b223a --- /dev/null +++ b/public/code/algorithms/leetcode-daily/minend.cpp @@ -0,0 +1,16 @@ +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 the bit should be set, set it-otherwise, leave it alone + if ((bits_to_distribute & 1) == 1) + x |= mask; + bits_to_distribute >>= 1; + } + mask <<= 1; + } + + return x; +} diff --git a/public/code/algorithms/leetcode-daily/msl-bitwise.py b/public/code/algorithms/leetcode-daily/msl-bitwise.py new file mode 100644 index 0000000..148a4b2 --- /dev/null +++ b/public/code/algorithms/leetcode-daily/msl-bitwise.py @@ -0,0 +1,30 @@ +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] += 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 diff --git a/public/code/algorithms/leetcode-daily/msl-naive.py b/public/code/algorithms/leetcode-daily/msl-naive.py new file mode 100644 index 0000000..509c6d0 --- /dev/null +++ b/public/code/algorithms/leetcode-daily/msl-naive.py @@ -0,0 +1,18 @@ +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 diff --git a/scripts/index.js b/scripts/index.js index 24fb812..ba762f7 100644 --- a/scripts/index.js +++ b/scripts/index.js @@ -19,6 +19,7 @@ const postMapping = new Map([ [ "Algorithms", [ + { name: "leetcode daily", link: "leetcode-daily" }, { name: "extrema circular buffer", link: "extrema-circular-buffer" }, ], ],