-
-
-
understanding the problem
-
- p 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}}\cdot4^{\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.
-
-
-
-
-
-
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:
-
-
- - Time Complexity: \(\O(n)\)
- - Space Complexity: \(\Theta(1)\)
-
-
-
-
-
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:
-
-
- -
- If an interviewer says “assume
nums is
- sorted” or “how can we do
- better?”—you're cooked.
-
- -
- (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
-
- 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.
-
-
-
-
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:
-
-
- - Items with a non-greater price
- - 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'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.
-
-
-
-
-
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:
-
-
- - 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.
-
-
-
-
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.
-
-
-
-
-
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)\).
-
-
-