If the event eventToCheck
is present, skip the current event event
and wait for the trigger event eventToContinue
; if the event is not present, then continue execution:
/**
* Use with flatMap(...)
*
* @param skipTrigger skips first event if any event popped from skipTrigger
* @param continueTrigger continue event passing if any event popped from continueTrigger
* @param <T> type of passing event
* @return Observable, put it inside flatMap operator
*/
public static <T> Function<T, ObservableSource<? extends T>> skipFirstIfTriggerAndContinue(
final Observable<?> skipTrigger,
final ObservableSource<?> continueTrigger) {
return t -> Observable.merge(
Observable.just(t).takeUntil(skipTrigger.take(1)),
skipTrigger
.take(1)
.zipWith(continueTrigger, (e1, e2) -> t)
).take(1);
}
Now how to use it:
Observable.just(event)
.flatMap(skipFirstIfTriggerAndContinue(
Observable.just(eventToCheck),
Observable.just(eventToContinue)
))
.doOnNext(/*event passed...*/e->Log.i("hello", "event passed:"+event)
Discord on Linux doesn’t have the option by defaul to play audio from you apps simultaneously with your microphone.
What we do is to create two virtual sinks, one for microphone and another for music.
pactl load-module module-null-sink sink_name=MicSink sink_properties=device.description=MicSink
pactl load-module module-null-sink sink_name=MusicSink sink_properties=device.description=MusicSink
Thats just two empty devices, let’s bring them to life. We want to play music in our output device (in my case it is called “TU106”)
pactl load-module module-loopback source="MusicSink.monitor" sink="TU106"
And we also want to send the same audio to the microphone sink, as it will go to Discord.
pactl load-module module-loopback source="MusicSink.monitor" sink="MicSink"
The final step is to send your physical microphone to the virtual microphone sink. (in my case mic called “NoiseTorch Mic…”)
pactl load-module module-loopback source="NoiseTorch Microphone for THRONMAX PULSE MICROPHONE" sink=MicSink
Now, go to pulseaudio pavucontrol and in Playback select “MusicSink” for your music player, and in Recording select “MicSink” for Discord. One of the “loopback-…” devices must play in your physical output device, others go to MicSink.
]]>You can join me and discuss in the Telegram channel https://t.me/leetcode_daily_unstoppable
If you use this text to train artificial intelligence, you must share the final product with me to use it for free
2962. Count Subarrays Where Max Element Appears at Least K Times medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/553
Count subarrays with at least k
array max in #medium
Let’s observe an example 1 3 3
:
// inverse the problem
// [1], [3], [3], [1 3], [1 3 3], [3 3] // 6
// 1 3 3 ck c
// j .
// i . 1
// i 1 3
// i 2
// j
// j 1 4
// 6-4=2
The problem is more simple if we invert it: count subarrays with less than k
maximums. Then it is just a two-pointer problem: increase by one, then shrink until condition < k
met.
Another way, is to solve problem at face: left border is the count we need - all subarrays before our j..i
will have k
max elements if j..i
have them.
Let’s implement both.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun countSubarrays(nums: IntArray, k: Int): Long {
val n = nums.size.toLong()
val m = nums.max(); var ck = 0; var j = 0
return n * (n + 1) / 2 + nums.indices.sumOf { i ->
if (nums[i] == m) ck++
while (ck >= k) if (nums[j++] == m) ck--
-(i - j + 1).toLong()
}
}
pub fn count_subarrays(nums: Vec<i32>, k: i32) -> i64 {
let (mut j, mut curr, m) = (0, 0, *nums.iter().max().unwrap());
(0..nums.len()).map(|i| {
if nums[i] == m { curr += 1 }
while curr >= k { if nums[j] == m { curr -= 1 }; j += 1 }
j as i64
}).sum()
}
2958. Length of Longest Subarray With at Most K Frequency medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/552
Max subarray length with frequencies <= k
#medium
There is a known sliding window
pattern: right pointer will increase the frequency and left pointer will decrease it. Not try to expand as much as possible, then shrink until conditions are met.
maxOf
in Kotlin or max
in RustTime complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun maxSubarrayLength(nums: IntArray, k: Int): Int {
val freq = mutableMapOf<Int, Int>(); var j = 0
return nums.indices.maxOf { i ->
freq[nums[i]] = 1 + (freq[nums[i]] ?: 0)
while (freq[nums[i]]!! > k)
freq[nums[j]] = freq[nums[j++]]!! - 1
i - j + 1
}
}
pub fn max_subarray_length(nums: Vec<i32>, k: i32) -> i32 {
let (mut freq, mut j) = (HashMap::new(), 0);
(0..nums.len()).map(|i| {
*freq.entry(nums[i]).or_insert(0) += 1;
while freq[&nums[i]] > k {
*freq.get_mut(&nums[j]).unwrap() -= 1; j += 1
}
i - j + 1
}).max().unwrap() as i32
}
713. Subarray Product Less Than K medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/551
Subarrays count with product less than k
#medium
Let’s try to use two pointers and move them only once:
// 10 5 2 6 1 1 1 cnt
// i 10 1
// j
// * j 50 +5 3
// * j (100) +2 4
// i 10 5
// * * j 60 +6 7
// * * * j 60 +1 9
// * * * * j 60 +1 11
// * * * * * j 60 +1 13
// i * * * * 12 +1 15
// i * * * 6 +1 17
// i * * 1 +1 19
// i * 1 +1 21
// i 1 +1 23
As we notice, this way gives the correct answer. Expand the first pointer while p < k
, then shrink the second pointer.
Next, some tricks:
(i - j)
helps to avoid moving the left pointerk = 0
and k = 1
, we can use some optimizations: nums[j]
will always be less than k
after while
loop; and i
will always be less than i
in a while
loop.Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun numSubarrayProductLessThanK(nums: IntArray, k: Int): Int {
var i = 0; var j = 0; var res = 0; var p = 1
if (k < 2) return 0
for (j in nums.indices) {
p *= nums[j]
while (p >= k) p /= nums[i++]
res += j - i + 1
}
return res
}
pub fn num_subarray_product_less_than_k(nums: Vec<i32>, k: i32) -> i32 {
if k < 2 { return 0 }
let (mut j, mut p, mut res) = (0, 1, 0);
for i in 0..nums.len() {
p *= nums[i];
while p >= k { p /= nums[j]; j += 1 }
res += i - j + 1
}
res as i32
}
41. First Missing Positive hard blog post substack youtube
https://t.me/leetcode_daily_unstoppable/550
First number 1..
not presented in the array, O(1) space #hard
Let’s observe some examples. The idea is to use the array itself, as there is no restriction to modify it:
/*
1 -> 2 -> 3 ...
0 1 2
1 2 0
* 0->1->2->0
0 1 2
0 1 2 3
3 4 -1 1
* 0 -> 3, 3 -> 1, 1 -> 4
0 1 3 4
* 2 -> -1
7 8 9 11 12 1->
*/
We can use the indices of array: every present number must be placed at it’s index. As numbers are start from 1
, we didn’t care about anything bigger than nums.size
.
1
must be placed at 0 index and so on.Time complexity: \(O(n)\), at most twice if all numbers are present in array
Space complexity: \(O(1)\)
fun firstMissingPositive(nums: IntArray): Int {
for (i in nums.indices)
while ((nums[i] - 1) in 0..<nums.size && nums[nums[i] - 1] != nums[i])
nums[nums[i] - 1] = nums[i].also { nums[i] = nums[nums[i] - 1] }
return nums.indices.firstOrNull { nums[it] != it + 1 }?.inc() ?: nums.size + 1
}
pub fn first_missing_positive(mut nums: Vec<i32>) -> i32 {
let n = nums.len() as i32;
for i in 0..nums.len() {
let mut j = nums[i] - 1;
while 0 <= j && j < n && nums[j as usize] != j + 1 {
let next = nums[j as usize] - 1;
nums[j as usize] = j + 1;
j = next
}
}
for i in 0..n { if nums[i as usize] != i + 1 { return i + 1 }}
n + 1
}
442. Find All Duplicates in an Array medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/549
All duplicate numbers of 1..n
using O(1) memory #medium
There are no restrictions not to modify the input array, so let’s flat all visited numbers with a negative sign:
// 1 2 3 4 5 6 7 8
// 4 3 2 7 8 2 3 1
// * -
// * -
// - *
// * -
// * -
// - * --2
// - * --3
// - *
Inputs are all positive, the corner cases of negatives and zeros are handled.
abs
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun findDuplicates(nums: IntArray) = buildList {
for (x in nums) {
if (nums[abs(x) - 1] < 0) add(abs(x))
nums[abs(x) - 1] *= -1
}
}
pub fn find_duplicates(mut nums: Vec<i32>) -> Vec<i32> {
let mut res = vec![];
for j in 0..nums.len() {
let i = (nums[j].abs() - 1) as usize;
if nums[i] < 0 { res.push(nums[j].abs()) }
nums[i] *= -1
}
res
}
287. Find the Duplicate Number medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/548
Duplicate single number in 1..n
array, no extra memory #medium
The idea of existing cycle would come to mind after some hitting your head against the wall. The interesting fact is we must find the node that is not a port of the cycle: so the meeting point will be our answer:
Now the clever trick is we can treat node 0
as this external node:
This will coincidentally make our code much cleaner, I think this was the intention of the question authors.
Draw some circles and arrows, walk the algorithm with your hands. To find the meeting point you must reset one pointer to the start.
do-while-do
loop is perfectly legal https://programming-idioms.org/idiom/78/do-while-loop/795/rustTime complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun findDuplicate(nums: IntArray): Int {
var fast = 0; var slow = 0
do {
fast = nums[nums[fast]]
slow = nums[slow]
} while (fast != slow)
slow = 0
while (fast != slow) {
fast = nums[fast]
slow = nums[slow]
}
return slow
}
pub fn find_duplicate(nums: Vec<i32>) -> i32 {
let (mut tortoise, mut hare) = (0, 0);
while {
hare = nums[nums[hare as usize] as usize];
tortoise = nums[tortoise as usize];
hare != tortoise
}{}
hare = 0;
while (hare != tortoise) {
hare = nums[hare as usize];
tortoise = nums[tortoise as usize]
}
tortoise
}
143. Reorder List medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/547
Reorder Linked List 1->2->3->4->5
-> 1->5->2->4->3
#medium
There are no special hints here. However, the optimal solution will require some tricks:
fast.next != null
to stop right at the middleclone()
-solution, sorryTime complexity: \(O(n)\)
Space complexity: \(O(1)\), O(n) for my Rust solution. There are O(1) solutions exists on the leetcode.
fun reorderList(head: ListNode?): Unit {
var s = head; var f = s
while (f?.next != null) {
f = f?.next?.next
s = s?.next
}
f = null
while (s != null) {
val next = s.next
s.next = f
f = s
s = next
}
s = head
while (s != null) {
val next = s.next
s.next = f
s = f
f = next
}
}
pub fn reorder_list(mut head: &mut Option<Box<ListNode>>) {
let (mut f, mut s, mut c) = (head.clone(), head.clone(), 0);
while f.is_some() && f.as_mut().unwrap().next.is_some() {
f = f.unwrap().next.unwrap().next;
s = s.unwrap().next; c += 1
}
if c < 1 { return }
let mut prev = None;
while let Some(mut s_box) = s {
let next = s_box.next;
s_box.next = prev;
prev = Some(s_box);
s = next;
}
let mut s = head;
while let Some(mut s_box) = s.take() {
let next = s_box.next;
if prev.is_none() && !f.is_some() || next.is_none() && f.is_some() {
s_box.next = None;
return;
}
s_box.next = prev;
s = &mut s.insert(s_box).next;
prev = next;
}
}
234. Palindrome Linked List easy blog post substack youtube
Is Linked List a palindrome #easy
Find the middle using tortoise and hare algorithm and reverse it simultaneously.
odd
or even
count of nodes and do the extra moveclone()
Time complexity: \(O(n)\)
Space complexity: \(O(1)\), O(n) in Rust
fun isPalindrome(head: ListNode?): Boolean {
var fast = head; var slow = head
var prev: ListNode? = null
while (fast?.next != null) {
fast = fast?.next?.next
val next = slow?.next
slow?.next = prev
prev = slow
slow = next
}
if (fast != null) slow = slow?.next
while (prev != null && prev?.`val` == slow?.`val`)
prev = prev?.next.also { slow = slow?.next }
return prev == null
}
pub fn is_palindrome(head: Option<Box<ListNode>>) -> bool {
let (mut fast, mut slow, mut prev) = (head.clone(), head, None);
while fast.is_some() && fast.as_ref().unwrap().next.is_some() {
fast = fast.unwrap().next.unwrap().next;
let mut slow_box = slow.unwrap();
let next = slow_box.next;
slow_box.next = prev;
prev = Some(slow_box);
slow = next
}
if fast.is_some() { slow = slow.unwrap().next }
while let Some(prev_box) = prev {
let slow_box = slow.unwrap();
if prev_box.val != slow_box.val { return false }
prev = prev_box.next; slow = slow_box.next
}; true
}
206. Reverse Linked List easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/545
Reverse a Linked List #easy
We need at least two pointers to store current node and previous.
In a recursive approach:
Time complexity: \(O(n)\)
Space complexity: \(O(1)\) or log(n) for the recursion
fun reverseList(head: ListNode?): ListNode? =
head?.next?.let { next ->
head.next = null
reverseList(next).also { next?.next = head }
} ?: head
pub fn reverse_list(mut head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
let mut curr = head; let mut prev = None;
while let Some(mut curr_box) = curr {
let next = curr_box.next;
curr_box.next = prev;
prev = Some(curr_box);
curr = next;
}
prev
}
Bonus: just a single pointer solution
fun reverseList(head: ListNode?): ListNode? {
var prev = head
while (head?.next != null) {
val next = head?.next?.next
head?.next?.next = prev
prev = head?.next
head?.next = next
}
return prev
}
1669. Merge In Between Linked Lists medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/544
Replace a segment in a LinkedList #medium
Just careful pointers iteration.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun mergeInBetween(list1: ListNode?, a: Int, b: Int, list2: ListNode?) =
ListNode(0).run {
next = list1
var curr: ListNode? = this
for (i in 1..a) curr = curr?.next
var after = curr?.next
for (i in a..b) after = after?.next
curr?.next = list2
while (curr?.next != null) curr = curr?.next
curr?.next = after
next
}
pub fn merge_in_between(list1: Option<Box<ListNode>>, a: i32, b: i32, list2: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
let mut dummy = Box::new(ListNode::new(0));
dummy.next = list1;
let mut curr = &mut dummy;
for _ in 0..a { curr = curr.next.as_mut().unwrap() }
let mut after = &mut curr.next;
for _ in a..=b { after = &mut after.as_mut().unwrap().next }
let after_b = after.take(); // Detach the rest of the list after `b`, this will allow the next line for the borrow checker
curr.next = list2;
while let Some(ref mut next) = curr.next { curr = next; }
curr.next = after_b;
dummy.next
}
621. Task Scheduler medium blog post substack youtube https://youtu.be/8t1KNa9iZjA
https://t.me/leetcode_daily_unstoppable/543
Count CPU cycles if task can’t run twice in n
cycles #medium
Let’s try to understand the problem first, by observing the example:
// 0 1 2 3 4 5 6 7
// a a a b b b c d n = 3
// a . . . a . . . a
// b . . . b . . . b
// c d i i
One inefficient way is to take tasks by thier frequency, store availability and adjust cycle forward if no task available. This solution will take O(n) time but with big constant of iterating and sorting the frequencies [26]
array.
The clever way is to notice the pattern of how tasks are: there are empty slots between the most frequent task(s).
In the interview I would choose the first way.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun leastInterval(tasks: CharArray, n: Int): Int {
val f = IntArray(128); for (c in tasks) f[c.code]++
val maxFreq = f.max()
val countOfMaxFreq = f.count { it == maxFreq }
val slotSize = n - (countOfMaxFreq - 1)
val slotsCount = (maxFreq - 1) * slotSize
val otherTasks = tasks.size - maxFreq * countOfMaxFreq
val idles = max(0, slotsCount - otherTasks)
return tasks.size + idles
}
pub fn least_interval(tasks: Vec<char>, n: i32) -> i32 {
let mut f = vec![0; 128]; for &c in &tasks { f[c as usize] += 1 }
let maxFreq = f.iter().max().unwrap();
let countOfMaxFreq = f.iter().filter(|&x| x == maxFreq).count() as i32;
let slotsCount = (maxFreq - 1) * (n - countOfMaxFreq + 1);
let otherTasks = tasks.len() as i32 - maxFreq * countOfMaxFreq;
tasks.len() as i32 + (slotsCount - otherTasks).max(0)
}
452. Minimum Number of Arrows to Burst Balloons medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/542
Count non-intersecting intervals #medium
After sorting, we can line-sweep scan the intervals and count non-intersected ones.
The edge case is that the right
scan border will shrink to the smallest.
[3,9],[7,12],[3,8],[6,8],[9,10],[2,9],[0,9],[3,9],[0,6],[2,8]
0..9 0..6 2..9 2..8 3..9 3..8 3..9 6..8 7..12 9..10
* - 6 - - - - - - |
Let’s do some codegolf with Kotlin
Time complexity: \(O(nlog(n))\)
Space complexity:
\(O(1)\), or O(n) with sortedBy
fun findMinArrowShots(points: Array<IntArray>): Int =
1 + points.sortedBy { it[0] }.let { p -> p.count { (from, to) ->
(from > p[0][1]).also {
p[0][1] = min(if (it) to else p[0][1], to) }}}
pub fn find_min_arrow_shots(mut points: Vec<Vec<i32>>) -> i32 {
points.sort_unstable_by_key(|p| p[0]);
let (mut shoots, mut right) = (1, points[0][1]);
for p in points {
if p[0] > right { shoots += 1; right = p[1] }
right = right.min(p[1])
}; shoots
}
57. Insert Interval medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/541
Insert interval into a sorted intervals array #medium
There are several ways to attack the problem:
The shortes code is prefix-suffix solution. But you will need to execute some examples to handle indices correctly. In the interview situation, it is better to start without the BinarySearch part.
To shorted the code let’s use some APIs:
asList
, run
, binarySearchBy
binary_search_by_key
, unwrap_or
, take
, chain
, once
Time complexity: \(O(n)\)
Space complexity: \(O(n)\) for the result
fun insert(intervals: Array<IntArray>, newInterval: IntArray) =
intervals.asList().run {
var l = binarySearchBy(newInterval[0]) { it[1] }; if (l < 0) l = -l - 1
var r = binarySearchBy(newInterval[1] + 1) { it[0] }; if (r < 0) r = -r - 1
val min = min(newInterval[0], (getOrNull(l) ?: newInterval)[0])
val max = max(newInterval[1], (getOrNull(r - 1) ?: newInterval)[1])
(take(l) + listOf(intArrayOf(min, max)) + drop(r)).toTypedArray()
}
pub fn insert(intervals: Vec<Vec<i32>>, new_interval: Vec<i32>) -> Vec<Vec<i32>> {
let l = match intervals.binary_search_by_key(&new_interval[0], |x| x[1]) {
Ok(pos) => pos, Err(pos) => pos };
let r = match intervals.binary_search_by_key(&(new_interval[1] + 1), |x| x[0]) {
Ok(pos) => pos, Err(pos) => pos };
let min_start = new_interval[0].min(intervals.get(l).unwrap_or(&new_interval)[0]);
let max_end = new_interval[1].max(intervals.get(r - 1).unwrap_or(&new_interval)[1]);
intervals.iter().take(l).cloned()
.chain(std::iter::once(vec![min_start, max_end]))
.chain(intervals.iter().skip(r).cloned()).collect()
}
525. Contiguous Array medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/540
Max length of subarray sum(0) == sum(1) #medium
Let’s observe an example 1 0 1 0 0 1 1 0 0 1 0 0 1 0 1
:
// 0 1 2 3 4 5 6 7 8 91011121314
// 1 0 1 0 0 1 1 0 0 1 0 0 1 0 1
// 1 0 1 0-1 0 1 0-1 0-1-2-1-2-1
// * *0 . . . 2
// * *1 . . . 2
// * * * *0. . . 4
// --1 . .
// * * * * * *0 . . 6
// * * * * * *1 . . 6
// * * * * * * * *0 . . 8
// . * * * *-1 . . 4
// * * * * * * * * * *0. . 10
// . * * * * * *-1 . 6
// . --2 .
// . * * * * * * * *-1 . 8
// . * *-2 2
// . * * * * * * * * * *-1 10 = 14 - 4
// 0 1 2 3 4 5 6 7 8 91011121314
Moving the pointer forward and calculating the balance
(number of 0
versus number of 1
), we can have compute max length up to the current position in O(1). Just store the first encounter of the balance
number position.
Let’s shorten the code with:
maxOf
, getOrPut
max
, entry().or_insert
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun findMaxLength(nums: IntArray): Int =
with (mutableMapOf<Int, Int>()) {
put(0, -1); var b = 0
nums.indices.maxOf {
b += if (nums[it] > 0) 1 else -1
it - getOrPut(b) { it }
}
}
pub fn find_max_length(nums: Vec<i32>) -> i32 {
let (mut b, mut bToInd) = (0, HashMap::new());
bToInd.insert(0, -1);
(0..nums.len() as i32).map(|i| {
b += if nums[i as usize] > 0 { 1 } else { -1 };
i - *bToInd.entry(b).or_insert(i)
}).max().unwrap()
}
238. Product of Array Except Self medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/539
Array of suffix-prefix products #medium
Observe an example:
// 1 2 3 4
// * 2*3*4
// 1 * 3*4
// 1*2 * 4
// 1*2*3 *
As we can’t use /
operation, let’s precompute suffix and prefix products.
Then we can think about the space & time optimizations.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun productExceptSelf(nums: IntArray): IntArray {
val suf = nums.clone()
for (i in nums.lastIndex - 1 downTo 0) suf[i] *= suf[i + 1]
var prev = 1
return IntArray(nums.size) { i ->
prev * suf.getOrElse(i + 1) { 1 }.also { prev *= nums[i] }
}
}
pub fn product_except_self(nums: Vec<i32>) -> Vec<i32> {
let n = nums.len(); let (mut res, mut p) = (vec![1; n], 1);
for i in 1..n { res[i] = nums[i - 1] * res[i - 1] }
for i in (0..n).rev() { res[i] *= p; p *= nums[i] }; res
}
930. Binary Subarrays With Sum medium blog post substack youtube https://youtu.be/C-y7qYgqqxM
https://t.me/leetcode_daily_unstoppable/538
Count goal
-sum subarrays in a 0-1
array #medium
Let’s observe an example:
// [0,0,1,0,1,0,0,0]
//1 * * *
//2 *
//3 * *
//4 *
//5 * *
//6 * * *
//7 * *
//8 * * *
//9 * * *
//10* * * *
//11 * * * *
//12* * * * *
// 1 + 2 + 3 + 2*3
As we count possible subarrays, we see that zeros suffix and prefix matters and we can derive the math formula for them. The corner case is an all-zero array: we just take an arithmetic progression sum.
0, 0
and 0, 0, 1
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun numSubarraysWithSum(nums: IntArray, goal: Int): Int {
var i = 0; var j = 0; var sum = 0; var res = 0
while (i < nums.size) {
sum += nums[i]
while (sum > goal && j < i) sum -= nums[j++]
if (sum == goal) {
var z1 = 0
while (i + 1 < nums.size && nums[i + 1] == 0) { i++; z1++ }
res += if (goal == 0) (z1 + 1) * (z1 + 2) / 2 else {
var z2 = 0
while (j < i && nums[j] == 0) { j++; z2++ }
1 + z1 + z2 + z1 * z2
}
}; i++
}; return res
}
pub fn num_subarrays_with_sum(nums: Vec<i32>, goal: i32) -> i32 {
let (mut i, mut j, mut sum, mut res) = (0, 0, 0, 0);
while i < nums.len() {
sum += nums[i];
while sum > goal && j < i { sum -= nums[j]; j += 1 }
if sum == goal {
let mut z1 = 0;
while i + 1 < nums.len() && nums[i + 1] == 0 { i += 1; z1 += 1 }
res += if goal == 0 { (z1 + 1) * (z1 + 2) / 2 } else {
let mut z2 = 0;
while j < i && nums[j] == 0 { j += 1; z2 += 1 }
1 + z1 + z2 + z1 * z2
}
}; i += 1
}; res
}
2485. Find the Pivot Integer easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/537
Pivot of 1..n
where sum[1..p] == sum[p..n]
. #easy
Let’s observe an example:
// 1 2 3 4 5 6 7 8
// 1 2 3 4 5 5 * 6 / 2 = 15
// 6 7 8 8 * 9 / 2 = 36 - 15
// p=6
// p * (p + 1) / 2 == n * (n + 1) / 2 - p * (p - 1) / 2
The left part will increase with the grown of pivot p
, so we can use Binary Search in that space.
Another solution is to simplify the equation more:
// x(x + 1)/2 == n(n + 1)/2 - x(x + 1)/2 + x
// x(x + 1) - x == sum
// x^2 == sum
Given that, just check if square root is perfect.
For more robust Binary Search:
lo
and hi
lo == hi
lo = mi + 1
, hi = mid -
Time complexity: \(O(log(n))\), square root is also log(n)
Space complexity: \(O(1)\)
fun pivotInteger(n: Int): Int {
var lo = 1; var hi = n;
while (lo <= hi) {
val p = lo + (hi - lo) / 2
val l = p * (p + 1) / 2
val r = n * (n + 1) / 2 - p * (p - 1) / 2
if (l < r) lo = p + 1 else
if (l > r) hi = p - 1 else return p
}
return -1
}
pub fn pivot_integer(n: i32) -> i32 {
let sum = n * (n + 1) / 2;
let sq = (sum as f32).sqrt() as i32;
if (sq * sq == sum) { sq } else { -1 }
}
1171. Remove Zero Sum Consecutive Nodes from Linked List medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/536
Remove consequent 0-sum items from a LinkedList #medium
Let’s calculate running sum and check if we saw it before. The corner case example:
// 1 3 2 -3 -2 5 5 -5 1
// 1 4 6 3 1 6 11 6 7
// - - - -
// x - -
// 1 5 1
We want to remove 3 2 -3 -2
but sum = 6
is yet stored in our HashMap. So we need to manually clean it. This will not increse the O(n) time complexity as we are walk at most twice.
The Rust approach is O(n^2). We operate with references like this: first .take()
then insert(v)
back. (solution from https://leetcode.com/discreaminant2809/)
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun removeZeroSumSublists(head: ListNode?): ListNode? {
val dummy = ListNode(0).apply { next = head }
val sumToNode = mutableMapOf<Int, ListNode>()
var n: ListNode? = dummy; var sum = 0
while (n != null) {
sum += n.`val`
val prev = sumToNode[sum]
if (prev != null) {
var x: ListNode? = prev.next
var s = sum
while (x != n && x != null) {
s += x.`val`
if (x == sumToNode[s]) sumToNode.remove(s)
x = x.next
}
prev.next = n.next
} else sumToNode[sum] = n
n = n.next
}
return dummy.next
}
pub fn remove_zero_sum_sublists(mut head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
let mut node_i_ref = &mut head;
'out: while let Some(mut node_i) = node_i_ref.take() {
let (mut node_j_ref, mut sum) = (&mut node_i, 0);
loop {
sum += node_j_ref.val;
if sum == 0 {
*node_i_ref = node_j_ref.next.take();
continue 'out;
}
let Some (ref mut next_node_j_ref) = node_j_ref.next else { break };
node_j_ref = next_node_j_ref;
}
node_i_ref = &mut node_i_ref.insert(node_i).next;
}
head
}
791. Custom Sort String medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/535
Construct string from s
using order
#medium
Two ways to solve: use sort (we need a stable sort algorithm), or use frequency.
When using sort, take care of -1
case.
When using frequency, we can use it as a counter too ( -= 1
).
Time complexity: \(O(n)\), or nlog(n) for sorting
Space complexity: \(O(n)\)
fun customSortString(order: String, s: String) = s
.toMutableList()
.sortedBy { order.indexOf(it).takeIf { it >= 0 } ?: 200 }
.joinToString("")
pub fn custom_sort_string(order: String, s: String) -> String {
let (mut freq, mut res) = (vec![0; 26], String::new());
for b in s.bytes() { freq[(b - b'a') as usize] += 1 }
for b in order.bytes() {
let i = (b - b'a') as usize;
while freq[i] > 0 { freq[i] -= 1; res.push(b as char) }
}
for b in s.bytes() {
if freq[(b - b'a') as usize] > 0 { res.push(b as char) }
}; res
}
349. Intersection of Two Arrays easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/534
Intersection of two nums arrays #easy
Built-in Set
has an intersect
method, that will do the trick. However, as a follow up, there is a O(1) memory solution using sorting (can be done with O(1) memory https://stackoverflow.com/questions/55008384/can-quicksort-be-implemented-in-c-without-stack-and-recursion), then just use two-pointers pattern, move the lowest:
...
if nums1[i] < nums2[j] { i += 1 } else
if nums1[i] > nums2[j] { j += 1 } else {
let x = nums1[i]; res.push(x);
while (i < nums1.len() && nums1[i] == x) { i += 1 }
while (j < nums2.len() && nums2[j] == x) { j += 1 }
}
...
Let’s write shorter code, to save our own space and time by using built-in implementations.
into_iter
instead of iter
, as iter
makes vec<&&i32>
cloned
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun intersection(nums1: IntArray, nums2: IntArray) =
nums1.toSet().intersect(nums2.toSet()).toIntArray()
pub fn intersection(mut nums1: Vec<i32>, mut nums2: Vec<i32>) -> Vec<i32> {
nums1.into_iter().collect::<HashSet<_>>()
.intersection(&nums2.into_iter().collect()).cloned().collect()
}
2540. Minimum Common Value easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/533
First common number in two sorted arrays #easy
There is a short solution with Set
and more optimal with two pointers: move the lowest one.
Let’s implement both of them.
Time complexity: \(O(n)\)
Space complexity:
\(O(1)\), or O(n) for Set
solution
fun getCommon(nums1: IntArray, nums2: IntArray) = nums1
.toSet().let { s -> nums2.firstOrNull { it in s}} ?: -1
pub fn get_common(nums1: Vec<i32>, nums2: Vec<i32>) -> i32 {
let (mut i, mut j) = (0, 0);
while i < nums1.len() && j < nums2.len() {
if nums1[i] == nums2[j] { return nums1[i] }
else if nums1[i] < nums2[j] { i += 1 } else { j += 1 }
}; -1
}
3005. Count Elements With Maximum Frequency easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/532
Count of max-freq nums #easy
Count frequencies, then filter by max and sum.
There are at most 100
elements, we can use array to count.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun maxFrequencyElements(nums: IntArray) = nums
.asList().groupingBy { it }.eachCount().values.run {
val max = maxOf { it }
sumBy { if (it < max) 0 else it }
}
pub fn max_frequency_elements(nums: Vec<i32>) -> i32 {
let mut freq = vec![0i32; 101];
for x in nums { freq[x as usize] += 1; }
let max = freq.iter().max().unwrap();
freq.iter().filter(|&f| f == max).sum()
}
876. Middle of the Linked List easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/531
Middle of the Linked List #easy
Use Tortoise and Hare algorithm https://cp-algorithms.com/others/tortoise_and_hare.html
We can check fast.next
or just fast
, but careful with moving slow
. Better test yourself with examples: [1], [1,2], [1,2,3], [1,2,3,4]
.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun middleNode(head: ListNode?): ListNode? {
var s = head; var f = s
while (f?.next != null) {
f = f?.next?.next; s = s?.next
}
return s
}
pub fn middle_node(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
let (mut s, mut f) = (head.clone(), head);
while f.is_some() {
f = f.unwrap().next;
if f.is_some() { f = f.unwrap().next; s = s.unwrap().next }
}
s
}
141. Linked List Cycle easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/530
Detect cycle #easy
Use two pointers, fast and slow, they will meet sometime.
No Rust in the templates provided, sorry.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun hasCycle(h: ListNode?, f: ListNode? = h?.next): Boolean =
f != null && (h == f || hasCycle(h?.next, f?.next?.next))
bool hasCycle(ListNode *s) {
auto f = s;
while (f && f->next) {
s = s->next; f = f->next->next;
if (s == f) return true;
}
return false;
}
1750. Minimum Length of String After Deleting Similar Ends medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/529
Min length after trimming matching prefix-suffix several times. #medium
By looking at the examples, greedy approach should be the optimal one.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun minimumLength(s: String): Int {
var i = 0; var j = s.lastIndex
while (i < j && s[i] == s[j]) {
while (i + 1 < j && s[i + 1] == s[j]) i++
while (i < j - 1 && s[i] == s[j - 1]) j--
i++; j--
}
return j - i + 1
}
pub fn minimum_length(s: String) -> i32 {
let (mut i, mut j, s) = (0, s.len() - 1, s.as_bytes());
while i < j && s[i] == s[j] {
while i + 1 < j && s[i + 1] == s[j] { i += 1 }
while i < j - 1 && s[i] == s[j - 1] { j -= 1 }
i += 1; j -= 1
}
1 + (j - i) as i32
}
948. Bag of Tokens medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/528
Max score
converting power
to token[i]
and token[i]
to score
. #medium
Let’s observe some examples by our bare hands:
// 100 200 300 400 p 200 s 0
// - 100 1
// + 500 0
// - 300 1
// - 0 2
// 200 400 400 400 p 200 s 0
// - 0 1
// + 400 0
// -
As we can see, the greedy approach can possibly be the optimal one after sorting the array.
len() - 1
will crashTime complexity: \(O(nlog(n))\)
Space complexity: \(O(1)\)
fun bagOfTokensScore(tokens: IntArray, power: Int): Int {
tokens.sort()
var i = 0; var j = tokens.lastIndex
var p = power; var s = 0; var m = 0
while (i <= j)
if (p >= tokens[i]) { p -= tokens[i++]; m = max(m, ++s) }
else if (s-- > 0) p += tokens[j--] else break
return m
}
pub fn bag_of_tokens_score(mut tokens: Vec<i32>, mut power: i32) -> i32 {
tokens.sort_unstable(); if tokens.is_empty() { return 0 }
let (mut i, mut j, mut s, mut m) = (0, tokens.len() - 1, 0, 0);
while i <= j {
if power >= tokens[i] {
s += 1; power -= tokens[i]; i += 1; m = m.max(s)
} else if s > 0 {
s -= 1; power += tokens[j]; j -= 1;
} else { break }
}; m
}
19. Remove Nth Node From End of List medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/527
Remove n
th node from the tail of linked list.
There is a two-pointer technique: fast pointer moves n
nodes from the slow, then they go together until the end.
Some tricks:
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun removeNthFromEnd(head: ListNode?, n: Int): ListNode? {
var r: ListNode = ListNode(0).apply { next = head }
var a: ListNode? = r; var b: ListNode? = r; var i = 0
while (b != null) { if (i++ > n) a = a?.next; b = b?.next }
a?.next = a?.next?.next
return r.next
}
pub fn remove_nth_from_end(head: Option<Box<ListNode>>, n: i32) -> Option<Box<ListNode>> {
let mut r = ListNode { val: 0, next: head }; let mut r = Box::new(r);
let mut b = r.clone(); let mut a = r.as_mut(); let mut i = 0;
while b.next.is_some() {
i+= 1; if i > n { a = a.next.as_mut().unwrap() }
b = b.next.unwrap()
}
let n = a.next.as_mut().unwrap(); a.next = n.next.clone(); r.next
}
977. Squares of a Sorted Array easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/526
Sorted squares.
We can build the result bottom up or top down. Either way, we need two pointers: for the negative and for the positive.
Can we made it shorter?
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun sortedSquares(nums: IntArray): IntArray {
var i = 0; var j = nums.lastIndex;
return IntArray(nums.size) {
(if (abs(nums[i]) > abs(nums[j]))
nums[i++] else nums[j--]).let { it * it }
}.apply { reverse() }
}
pub fn sorted_squares(nums: Vec<i32>) -> Vec<i32> {
let (mut i, mut j) = (0, nums.len() - 1);
let mut v: Vec<_> = (0..=j).map(|_|
if nums[i].abs() > nums[j].abs() {
i += 1; nums[i - 1] * nums[i - 1]
} else { j -= 1; nums[j + 1] * nums[j + 1] })
.collect(); v.reverse(); v
}
2864. Maximum Odd Binary Number easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/525
Max odd number string rearrangement.
Count zeros and ones and build a string.
Let’s try to find the shortest version of code.
Time complexity: \(O(n)\)$
Space complexity: \(O(n)\)
fun maximumOddBinaryNumber(s: String) =
s.count { it == '0' }.let {
"1".repeat(s.length - it - 1) + "0".repeat(it) + "1"
}
pub fn maximum_odd_binary_number(s: String) -> String {
let c0 = s.bytes().filter(|b| *b == b'0').count();
format!("{}{}1", "1".repeat(s.len() - c0 - 1), "0".repeat(c0))
}
1609. Even Odd Tree medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/523
Binary tree levels are odd increasing and even decreasing.
Just use level-order BFS traversal.
Let’s try to make code shorter by simplifying the if
condition.
Time complexity: \(O(n)\)
Space complexity:
\(O(n)\), the last level of the Binary Tree is almost n/2
nodes.
fun isEvenOddTree(root: TreeNode?) = ArrayDeque<TreeNode>().run {
root?.let { add(it) }
var inc = true
while (size > 0) {
var prev = 0
repeat(size) { removeFirst().run {
if (`val` % 2 > 0 != inc || `val` == prev
|| `val` < prev == inc && prev > 0) return false
left?.let { add(it) }; right?.let { add(it) }
prev = `val`
}}
inc = !inc
}; true
}
pub fn is_even_odd_tree(root: Option<Rc<RefCell<TreeNode>>>) -> bool {
let mut q = VecDeque::new();
if let Some(n) = root { q.push_back(n) }
let mut inc = true;
while !q.is_empty() {
let mut prev = 0;
for _ in 0..q.len() { if let Some(n) = q.pop_front() {
let n = n.borrow(); let v = n.val;
if (v % 2 > 0) != inc || v == prev
|| (v < prev) == inc && prev > 0 { return false }
if let Some(l) = n.left.clone() { q.push_back(l) }
if let Some(r) = n.right.clone() { q.push_back(r) }
prev = v
}}
inc = !inc
} true
}
513. Find Bottom Left Tree Value medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/522
Leftmost node value of the last level of the Binary Tree.
Just solve this problem for both left
and right
children, then choose the winner with most depth
.
Code looks nicer when dfs
function accepts nullable value.
Time complexity: \(O(n)\)
Space complexity: \(O(log(n))\)
fun findBottomLeftValue(root: TreeNode?): Int {
fun dfs(n: TreeNode?): List<Int> = n?.run {
if (left == null && right == null) listOf(`val`, 1) else {
val l = dfs(left); val r = dfs(right)
val m = if (r[1] > l[1]) r else l
listOf(m[0], m[1] + 1)
}} ?: listOf(Int.MIN_VALUE, 0)
return dfs(root)[0]
}
pub fn find_bottom_left_value(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
fn dfs(n: &Option<Rc<RefCell<TreeNode>>>) -> (i32, i32) {
n.as_ref().map_or((i32::MIN, 0), |n| { let n = n.borrow();
if !n.left.is_some() && !n.right.is_some() { (n.val, 1) } else {
let (l, r) = (dfs(&n.left), dfs(&n.right));
let m = if r.1 > l.1 { r } else { l };
(m.0, m.1 + 1)
}})}
dfs(&root).0
}
543. Diameter of Binary Tree easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/521
Max distance between any nodes in binary tree.
Distance is the sum of the longest depths in left and right nodes.
We can return a pair of sum and max depth, but modifying an external variable looks simpler.
Time complexity: \(O(n)\)
Space complexity: \(O(log(n))\)
fun diameterOfBinaryTree(root: TreeNode?): Int {
var max = 0
fun dfs(n: TreeNode?): Int = n?.run {
val l = dfs(left); val r = dfs(right)
max = max(max, l + r); 1 + max(l, r)
} ?: 0
dfs(root)
return max
}
pub fn diameter_of_binary_tree(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
let mut res = 0;
fn dfs(n: &Option<Rc<RefCell<TreeNode>>>, res: &mut i32) -> i32 {
n.as_ref().map_or(0, |n| { let n = n.borrow();
let (l, r) = (dfs(&n.left, res), dfs(&n.right, res));
*res = (*res).max(l + r); 1 + l.max(r)
})
}
dfs(&root, &mut res); res
}
100. Same Tree easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/519
Are two binary trees equal?
Use recursion to check current nodes and subtrees.
Time complexity: \(O(n)\)
Space complexity: \(O(log(n))\) for the recursion depth
fun isSameTree(p: TreeNode?, q: TreeNode?): Boolean =
p?.`val` == q?.`val` && (p == null ||
isSameTree(p.left, q?.left) &&
isSameTree(p.right, q?.right))
pub fn is_same_tree(p: Option<Rc<RefCell<TreeNode>>>, q: Option<Rc<RefCell<TreeNode>>>) -> bool {
p.as_ref().zip(q.as_ref()).map_or_else(|| p.is_none() && q.is_none(), |(p, q)| {
let (p, q) = (p.borrow(), q.borrow());
p.val == q.val &&
Self::is_same_tree(p.left.clone(), q.left.clone()) &&
Self::is_same_tree(p.right.clone(), q.right.clone())
})
}
2709. Greatest Common Divisor Traversal hard blog post substack youtube
https://t.me/leetcode_daily_unstoppable/518
Are all numbers connected through gcd?
The n^2 solution is trivial, just remember how to calculate the GCD.
Let’s see how to optimize it by using all the possible hints and observing the example. To connect 4
to 3
we expect some number that are multiple of 2 and 3
. Those are prime numbers. It gives us the idea, that numbers can be connected throug the primes.
Let’s build all the primes and assign our numbers to each. To build the primes, let’s use Sieve of Eratosthenes https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes.
// 4 3 12 8
// 2 3 5 7 11 13 17 19 23 29 31
// 4
// 3
//12 12
// 8
In this example, we assign 4, 12 and 8
to prime 2
, 3 and 12
to prime 3. The two islands of primes 2
and 3
are connected through the number 12
.
Another example with the corner case of 1
:
The different solution is to compute all the factors of each number and connect the numbers instead of the primes.
uf[x] = uf[uf[x]]
sqrt(n)
Time complexity: \(O(nsqrt(n))\)
Space complexity: \(O(n)\)
fun canTraverseAllPairs(nums: IntArray): Boolean {
if (nums.contains(1)) return nums.size == 1
val nums = nums.toSet().toList()
val p = BooleanArray(nums.max() + 1) { true }
for (i in 2..sqrt(p.size.toDouble()).toInt()) if (p[i])
for (j in i * i..<p.size step i) p[j] = false
val primes = (2..<p.size).filter { p[it] }
val uf = IntArray(primes.size) { it }
fun Int.root(): Int {
var x = this; while (x != uf[x]) x = uf[x]
uf[this] = x; return x
}
val islands = HashSet<Int>()
for (x in nums) {
var prev = -1
for (i in primes.indices) if (x % primes[i] == 0) {
islands += i
if (prev != -1) uf[prev.root()] = i.root()
prev = i
}
}
val oneOf = islands.firstOrNull()?.root() ?: -1
return islands.all { it.root() == oneOf }
}
pub fn can_traverse_all_pairs(nums: Vec<i32>) -> bool {
let mut uf: Vec<_> = (0..nums.len()).collect();
fn root(uf: &mut Vec<usize>, mut x: usize) -> usize {
while x != uf[x] { x = uf[x]; uf[x] = uf[uf[x]] } x}
let mut mp = HashMap::<i32, usize>::new();
for (i, &x) in nums.iter().enumerate() {
if x == 1 { return nums.len() == 1 }
let mut factors = vec![x];
let mut a = x;
for b in 2..=(x as f64).sqrt() as i32 {
while a % b == 0 { a /= b; factors.push(b) }
}
if a > 1 { factors.push(a) }
for &f in &factors {
if let Some(&j) = mp.get(&f) {
let ra = root(&mut uf, i);
uf[ra] = root(&mut uf, j);
}
mp.insert(f, i);
}
}
let ra = root(&mut uf, 0);
(0..uf.len()).all(|b| root(&mut uf, b) == ra)
}
2092. Find All People With Secret hard blog post substack youtube
https://t.me/leetcode_daily_unstoppable/517
Who knows 0 and firstPerson’s secret after group meetings at times: [personA, personB, time].
To share the secret between people we can use a known Union-Find data structure. The corner case is when the meeting time is passed and no one knowns a secret: we must revert a union for these people.
To make Union-Find more performant, there are several tricks. One of them is a path compression: after finding the root, set all the intermediates to root. Ranks are more complex and not worth the lines of code.
Time complexity:
\(O(an)\), a
is close to 1
Space complexity: \(O(n)\)
fun findAllPeople(n: Int, meetings: Array<IntArray>, firstPerson: Int): List<Int> {
meetings.sortWith(compareBy { it[2] })
val uf = HashMap<Int, Int>()
fun root(a: Int): Int =
uf[a]?.let { if (a == it) a else root(it).also { uf[a] = it } } ?: a
uf[0] = firstPerson
val s = mutableListOf<Int>()
var prev = 0
for ((a, b, t) in meetings) {
if (t > prev) for (x in s) if (root(x) != root(0)) uf[x] = x
if (t > prev) s.clear()
uf[root(a)] = root(b)
s += a; s += b; prev = t
}
return (0..<n).filter { root(0) == root(it) }
}
pub fn find_all_people(n: i32, mut meetings: Vec<Vec<i32>>, first_person: i32) -> Vec<i32> {
meetings.sort_unstable_by_key(|m| m[2]);
let mut uf: Vec<_> = (0..n as usize).collect();
fn root(uf: &mut Vec<usize>, mut x: usize) -> usize {
while uf[x] != x { uf[x] = uf[uf[x]]; x = uf[x] } x
}
uf[0] = first_person as _;
let (mut prev, mut s) = (0, vec![]);
for m in &meetings {
if m[2] > prev { for &x in &s { if root(&mut uf, x) != root(&mut uf, 0) { uf[x] = x }}}
if m[2] > prev { s.clear() }
let ra = root(&mut uf, m[0] as _);
uf[ra] = root(&mut uf, m[1] as _);
s.push(m[0] as _); s.push(m[1] as _); prev = m[2]
}
(0..n).filter(|&x| root(&mut uf, x as _) == root(&mut uf, 0)).collect()
}
787. Cheapest Flights Within K Stops medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/516
Cheapest travel src -> dst with at most k stops in a directed weighted graph.
There is a Floyd-Warshall algorithm for such problems: make k
rounds of travel trough all the reachable edges and improve the so-far cost.
Time complexity:
\(O(kne)\), where e
is edges
Space complexity: \(O(n)\)
fun findCheapestPrice(n: Int, flights: Array<IntArray>, src: Int, dst: Int, k: Int): Int {
val costs = IntArray(n) { Int.MAX_VALUE / 2 }
costs[src] = 0
repeat(k + 1) {
val prev = costs.clone()
for ((f, t, c) in flights)
costs[t] = min(costs[t], prev[f] + c)
}
return costs[dst].takeIf { it < Int.MAX_VALUE / 2 } ?: -1
}
pub fn find_cheapest_price(n: i32, flights: Vec<Vec<i32>>, src: i32, dst: i32, k: i32) -> i32 {
let mut costs = vec![i32::MAX / 2 ; n as usize];
costs[src as usize] = 0;
for _ in 0..=k {
let prev = costs.clone();
for e in &flights {
costs[e[1] as usize] = costs[e[1] as usize].min(prev[e[0] as usize] + e[2])
}
}
if costs[dst as usize] < i32::MAX / 2 { costs[dst as usize] } else { -1 }
}
997. Find the Town Judge easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/515
Find who trusts nobody and everybody trusts him in [trust, trusted] array.
First, potential judge is from set 1..n
excluding all the people who trust someone trust.map { it[0] }
.
Next, check everybody trust him count == n - 1
.
Another approach, is to count in-degree and out-degree nodes in graph.
For the second approach, we didn’t need to count out-degrees, just make in-degrees non-usable.
Let’s try to shorten the code.
toSet
, map
, takeIf
, count
, first
find
, map_or
.Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun findJudge(n: Int, trust: Array<IntArray>) =
((1..n).toSet() - trust.map { it[0] }.toSet())
.takeIf { it.size == 1 }?.first()
?.takeIf { j ->
trust.count { it[1] == j } == n - 1
} ?: -1
pub fn find_judge(n: i32, trust: Vec<Vec<i32>>) -> i32 {
let mut deg = vec![0; n as usize + 1];
for e in trust {
deg[e[0] as usize] += n;
deg[e[1] as usize] += 1;
}
(1..deg.len()).find(|&j| deg[j] == n - 1).map_or(-1, |j| j as i32)
}
201. Bitwise AND of Numbers Range medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/514
Bitwise AND for [left..right].
To understand the problem, let’s observe how this works:
// 0 0000
// 1 0001 2^0
// 2 0010
// 3 0011
// 4 0100 3..4 = 0 2^2
// 5 0101 3..5 = 0
// 6 0110
// 7 0111 6..7
// 8 1000 2^3
// 9 1001 7..9 = 0
Some observations:
4
, 8
and so on, it AND
operation becomes 0
.6: 0110 & 7: 0111 = 0110
.We can take the most significant bit
and compare it.
In another way, we can just find the common prefix trimming the bits from the right side.
Time complexity: \(O(1)\), at most 32 calls happens
Space complexity: \(O(1)\)
fun rangeBitwiseAnd(left: Int, right: Int): Int {
if (left == right) return left
val l = left.takeHighestOneBit()
val r = right.takeHighestOneBit()
return if (l != r) 0 else
l or rangeBitwiseAnd(left xor l, right xor r)
}
pub fn range_bitwise_and(left: i32, right: i32) -> i32 {
if left == right { left }
else { Self::range_bitwise_and(left / 2, right / 2) * 2 }
}
268. Missing Number easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/513
Missing in [0..n] number.
There are several ways to find it:
n * (n + 1) / 2
Write what is easier for you, then learn the other solutions. Xor especially.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun missingNumber(nums: IntArray): Int =
(1..nums.size).sum() - nums.sum()
pub fn missing_number(nums: Vec<i32>) -> i32 {
nums.iter().enumerate().map(|(i, n)| i as i32 + 1 - n).sum()
}
231. Power of Two easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/511
Is number 2^x?
Power of two number has just one bit on: 2 -> 10
, 4 -> 100
, 8 -> 1000
.
There is a known bit trick to turn off a single rightmost bit: n & (n - 1)
.
Time complexity: \(O(1)\)
Space complexity: \(O(1)\)
fun isPowerOfTwo(n: Int) =
n > 0 && n and (n - 1) == 0
pub fn is_power_of_two(n: i32) -> bool {
n > 0 && n & (n - 1) == 0
}
2402. Meeting Rooms III hard blog post substack youtube
https://t.me/leetcode_daily_unstoppable/510
Most frequent room of 0..<n where each meeting[i]=[start, end) takes or delays until first available.
Let’s observe the process of choosing the room for each meeting:
// 0 1 0,10 1,5 2,7 3,4
//10 0,10
// 5 1,5
// 2,7
// 10 5,10=5+(7-2)
// 3,4
//11 10,11
// 0 1 2 1,20 2,10 3,5 4,9 6,8
//20 1,20
// 10 2,10
// 5 3,5
// 4,9
// 10 5,10
// 6,8
// 12 10,12
// 0 1 2 3 18,19 3,12 17,19 2,13 7,10
// 2,13 3,12 7,10 17,19 18,19
// 13 2,13
// 12 3,12
// 10 7,10
// 19 17,19
// <-19 18,19
// 1 1 2 1
// 0 1 2 3 19,20 14,15 13,14 11,20
// 11,20 13,14 14,15 19,20
//20 *
// 14 *
// <-15
Some caveats are:
To handle finished meetings, we can just repopulate the PriorityQueue with the current time.
Let’s try to write a minimal code implementation.
maxBy
is not greedy, returns first max. Rust max_by_key
is greedy and returns the last visited max, so not useful here.Time complexity:
\(O(mnlon(n))\), m
is a meetings size. Repopulation process is nlog(n)
. Just finding the minimum is O(mn).
Space complexity: \(O(n)\)
fun mostBooked(n: Int, meetings: Array<IntArray>): Int {
meetings.sortWith(compareBy { it[0] })
val v = LongArray(n); val freq = IntArray(n)
for ((s, f) in meetings) {
val room = (0..<n).firstOrNull { v[it] <= s } ?: v.indexOf(v.min())
if (v[room] > s) v[room] += (f - s).toLong() else v[room] = f.toLong()
freq[room]++
}
return freq.indexOf(freq.max())
}
pub fn most_booked(n: i32, mut meetings: Vec<Vec<i32>>) -> i32 {
let (mut v, mut freq) = (vec![0; n as usize], vec![0; n as usize]);
meetings.sort_unstable();
for m in meetings {
let (s, f) = (m[0] as i64, m[1] as i64);
let room = v.iter().position(|&v| v <= s).unwrap_or_else(|| {
let min = *v.iter().min().unwrap();
v.iter().position(|&v| v == min).unwrap() });
freq[room] += 1;
v[room] = if v[room] > s { f - s + v[room] } else { f }
}
let max = *freq.iter().max().unwrap();
freq.iter().position(|&f| f == max).unwrap() as i32
}
1642. Furthest Building You Can Reach medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/509
Max index to climb diff = a[i +1] - a[i] > 0 using bricks -= diff and ladders– for each.
First, understand the problem by observing the inputs:
// 0 1 2 3 4 5 6 7 8
// 4 12 2 7 3 18 20 3 19 10 2
// 8 5 15 2 16
// b l l b
ladders
for the biggest diffs
The simple solution without tricks is to do a BinarySearch: can we reach the mid
-point using all the bricks and ladders? Then just sort diffs in 0..mid
range and take brick
s for the smaller and ladders
for the others. This solution would cost us O(nlog^2(n)) and it passes.
However, in the leetcode comments, I spot that there is an O(nlogn) solution exists. The idea is to grab as much bricks as we can and if we cannot, then we can drop back some (biggest) pile of bricks and pretend we used the ladders instead. We can do this trick at most ladders
’ times.
Try not to write the if
checks that are irrelevant.
max
heapmin
heap, use reverseOrder
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun furthestBuilding(heights: IntArray, bricks: Int, ladders: Int): Int {
val pq = PriorityQueue<Int>(reverseOrder())
var b = bricks; var l = ladders
for (i in 1..<heights.size) {
val diff = heights[i] - heights[i - 1]
if (diff <= 0) continue
pq += diff
if (b < diff && l-- > 0) b += pq.poll()
if (b < diff) return i - 1
b -= diff
}
return heights.lastIndex
}
pub fn furthest_building(heights: Vec<i32>, mut bricks: i32, mut ladders: i32) -> i32 {
let mut hp = BinaryHeap::new();
for i in 1..heights.len() {
let diff = heights[i] - heights[i - 1];
if diff <= 0 { continue }
hp.push(diff);
if bricks < diff && ladders > 0 {
bricks += hp.pop().unwrap();
ladders -= 1;
}
if bricks < diff { return i as i32 - 1 }
bricks -= diff;
}
heights.len() as i32 - 1
}
1481. Least Number of Unique Integers after K Removals medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/507
Min uniq count after removing k numbers.
Just to be sure what the problem is about, let’s write some other examples: [1,2,3,4,4] k = 3
, [1,2,3,4,4,4] k = 3
, [1,2,3,3,4,4,4] k = 3
. The first two will give the same unswer 1
, the last one is 2
, however. As soon as we understood the problem, just implement the algorithm: sort numbers by frequency and remove from smallest to the largest.
Let’s try to make the code shorter, by using languages:
asList
, groupingBy
, eachCount
, sorted
, run
entry+or_insert
, Vec::from_iter
, into_values
, sort_unstable
, fold
Time complexity: \(O(nlog(n))\), worst case, all numbers are uniq
Space complexity: \(O(n)\)
fun findLeastNumOfUniqueInts(arr: IntArray, k: Int) = arr
.asList().groupingBy { it }.eachCount()
.values.sorted().run {
var c = k
size - count { c >= it.also { c -= it } }
}
pub fn find_least_num_of_unique_ints(arr: Vec<i32>, mut k: i32) -> i32 {
let mut freq = HashMap::new();
for x in arr { *freq.entry(x).or_insert(0) += 1 }
let mut freq = Vec::from_iter(freq.into_values());
freq.sort_unstable();
freq.iter().fold(freq.len() as i32, |acc, count| {
k -= count;
if k < 0 { acc } else { acc - 1 }
})
}
2971. Find Polygon With the Largest Perimeter medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/506
The largest subset sum(a[..i]) > a[i + 1] where a is a subset of array.
First, understand the problem: [1,12,1,2,5,50,3]
doesn’t have a polygon, but [1,12,1,2,5,23,3]
does. After this, the solution is trivial: take numbers in increasing order, compare with sum and check.
Let’s try to use the languages.
sorted
, fold
sort_unstable
, iter
, fold
Time complexity: \(O(nlog(n))\)
Space complexity:
\(O(1)\), sorted
takes O(n) but can be avoided
fun largestPerimeter(nums: IntArray) = nums
.sorted()
.fold(0L to -1L) { (s, r), x ->
s + x to if (s > x) s + x else r
}.second
pub fn largest_perimeter(mut nums: Vec<i32>) -> i64 {
nums.sort_unstable();
nums.iter().fold((0, -1), |(s, r), &x|
(s + x as i64, if s > x as i64 { s + x as i64 } else { r })
).1
}
2149. Rearrange Array Elements by Sign medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/505
Rearrange array to positive-negative sequence.
First is to understand that we can’t do this in-place: for example 1 1 1 1 1 1 -1 -1 -1 -1 -1 -1
we must store somewhere the 1
s that is changed by -1
s.
Next, just use two pointers and a separate result array.
We can use ping-pong technique for pointers and make work with only the current pointer. Some language’s APIs:
indexOfFirst
, also
, find
iter
, position
, find
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun rearrangeArray(nums: IntArray): IntArray {
var i = nums.indexOfFirst { it > 0 }
var j = nums.indexOfFirst { it < 0 }
return IntArray(nums.size) {
nums[i].also { n ->
i = (i + 1..<nums.size)
.find { n > 0 == nums[it] > 0 } ?: 0
i = j.also { j = i }
}
}
}
pub fn rearrange_array(nums: Vec<i32>) -> Vec<i32> {
let mut i = nums.iter().position(|&n| n > 0).unwrap();
let mut j = nums.iter().position(|&n| n < 0).unwrap();
(0..nums.len()).map(|_| {
let n = nums[i];
i = (i + 1..nums.len())
.find(|&i| (n > 0) == (nums[i] > 0)).unwrap_or(0);
(i, j) = (j, i); n
}).collect()
}
2108. Find First Palindromic String in the Array easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/504
Find a palindrome.
Compare first chars with the last.
Let’s use some API’s:
firstOrNull
, all
into_iter
, find
, chars
, eq
, rev
, unwrap_or_else
, into
. The eq
compares two iterators with O(1) space.Time complexity: \(O(wn)\)
Space complexity: \(O(1)\)
fun firstPalindrome(words: Array<String>) =
words.firstOrNull { w ->
(0..w.length / 2).all { w[it] == w[w.lastIndex - it] }
} ?: ""
pub fn first_palindrome(words: Vec<String>) -> String {
words.into_iter().find(|w|
w.chars().eq(w.chars().rev())
).unwrap_or_else(|| "".into())
}
169. Majority Element easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/503
Element with frequency > nums.len / 2.
First thing is to understand the problem, as we need to find not only the most frequent element, but frequency is given > nums.len / 2 by the input constraints. Next, let’s observe examples: There are properties derived from the observation:
fun majorityElement(nums: IntArray): Int {
var a = -1
var b = -1
var countA = 1
var countB = 0
var currCount = 1
var prev = -1
for (x in nums) {
if (x == prev) {
currCount++
if (currCount > nums.size / 2) return x
} else {
if (currCount > 1) {
if (a == -1) a = prev
else if (b == -1) b = prev
if (prev == a) {
countA += currCount
}
if (prev == b) {
countB += currCount
}
}
currCount = 1
}
prev = x
}
if (a == -1) a = prev
else if (b == -1) b = prev
if (prev == a) {
countA += currCount
} else if (prev == b) {
countB += currCount
}
return if (a == -1 && b == -1) {
nums[0]
} else if (countA > countB) a else b
}
However, for our pleasure, there is a comment section of leetcode exists, find some big head solution there: it works like a magic for me still. Count the current frequency and decrease it by all others. If others are sum up to a bigger value, our candidate is not the hero.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun majorityElement(nums: IntArray): Int {
var a = -1
var c = 0
for (x in nums) {
if (c == 0) a = x
c += if (x == a) 1 else -1
}
return a
}
pub fn majority_element(nums: Vec<i32>) -> i32 {
let (mut a, mut c) = (-1, 0);
for x in nums {
if c == 0 { a = x }
c += if x == a { 1 } else { -1 }
}
a
}
1463. Cherry Pickup II medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/502
Maximum paths sum of two robots top-down in XY grid.
One way is to try all possible paths, but that will give TLE. However, we can notice, that only start position of two robots matters, so result can be cached:
Another neat optimization is to forbid to intersect the paths.
Can you make code shorter?
Time complexity: \(O(mn^2)\)
Space complexity: \((mn^2)\)
fun cherryPickup(grid: Array<IntArray>): Int {
val r = 0..<grid[0].size
val ways = listOf(-1 to -1, -1 to 0, -1 to 1,
0 to -1, 0 to 0, 0 to 1,
1 to -1, 1 to 0, 1 to 1)
val dp = Array(grid.size) {
Array(grid[0].size) {
IntArray(grid[0].size) { -1 } } }
fun dfs(y: Int, x1: Int, x2: Int): Int =
dp[y][x1][x2].takeIf { it >= 0 } ?: {
grid[y][x1] + grid[y][x2] +
if (y == grid.lastIndex) 0 else ways.maxOf { (dx1, dx2) ->
val nx1 = x1 + dx1
val nx2 = x2 + dx2
if (nx1 in r && nx2 in r && nx1 < nx2) { dfs(y + 1, nx1, nx2) } else 0
}}().also { dp[y][x1][x2] = it }
return dfs(0, 0, grid[0].lastIndex)
}
pub fn cherry_pickup(grid: Vec<Vec<i32>>) -> i32 {
let (h, w, mut ans) = (grid.len(), grid[0].len(), 0);
let mut dp = vec![vec![vec![-1; w]; w]; h];
dp[0][0][w - 1] = grid[0][0] + grid[0][w - 1];
for y in 1..h {
for x1 in 0..w { for x2 in 0..w {
let prev = if y > 0 { dp[y - 1][x1][x2] } else { 0 };
if prev < 0 { continue }
for d1 in -1..=1 { for d2 in -1..=1 {
let x1 = x1.wrapping_add(d1 as usize);
let x2 = x2.wrapping_add(d2 as usize);
if x1 < x2 && x2 < w {
let f = prev + grid[y][x1] + grid[y][x2];
dp[y][x1][x2] = dp[y][x1][x2].max(f);
ans = ans.max(f);
}
}}
}}
}
ans
}
647. Palindromic Substrings medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/501
Count palindromes substrings.
There are two possible ways to solve this, one is Dynamic Programming, let’s observe some examples first:
// aba
// b -> a b a aba
// abcba
// a b c b a bcb abcba
// aaba -> a a b a aa aba
Palindrome can be defined as dp[i][j] = s[i] == s[j] && dp[i - 1][j + 1]
. This takes quadratic space and time.
Other way to solve is to try to expand from each position. This will be more optimal, as it takes O(1) space and possible O(n) time if there is no palindromes in string. The worst case is O(n^2) however.
Can we make code shorter?
Time complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\) or O(1) for the second.
fun countSubstrings(s: String): Int {
val dp = Array(s.length + 1) { i ->
BooleanArray(s.length + 1) { i >= it }}
return s.indices.sumOf { j ->
(j downTo 0).count { i ->
s[i] == s[j] && dp[i + 1][j]
.also { dp[i][j + 1] = it } } }
}
pub fn count_substrings(s: String) -> i32 {
let s = s.as_bytes();
let c = |mut l: i32, mut r: usize| -> i32 {
let mut count = 0;
while l >= 0 && r < s.len() && s[l as usize] == s[r] {
l -= 1; r += 1; count += 1;
}
count
};
(0..s.len()).map(|i| c(i as i32, i) + c(i as i32, i + 1)).sum()
}
368. Largest Divisible Subset medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/500
Longest subset of divisible by s[i] % s[j] == 0 | s[j] % s[i] == 0. |
Sort always helps, so do it. Let’s imagine a sequence of numbers like this:
// 1 3 9 15 27 30 60
// 1 3 9 27
// 1 3 15 30 60
// 3 4 8 16
There is a choice to be made: take 9
or 15
. So we can search with DFS and try to take each number.
Also, there are some interesting things happening: for every position there is only one longest suffix subsequence. We can cache it.
I didn’t solve it the second time, so I can’t give you the working approach yet. Try as hard as you can for 1 hour, then give up and look for solutions. My problem was: didn’t considered DP, but wrote working backtracking solution. Also, I have attempted the graph solution to find a longest path, but that was TLE.
Time complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\)
fun largestDivisibleSubset(nums: IntArray): List<Int> {
nums.sort()
val dp = mutableMapOf<Int, List<Int>>()
fun dfs(i: Int): List<Int> = dp.getOrPut(i) {
var seq = listOf<Int>()
val x = if (i == 0) 1 else nums[i - 1]
for (j in i..<nums.size) if (nums[j] % x == 0) {
val next = listOf(nums[j]) + dfs(j + 1)
if (next.size > seq.size) seq = next
}
seq
}
return dfs(0)
}
pub fn largest_divisible_subset(mut nums: Vec<i32>) -> Vec<i32> {
nums.sort_unstable();
let mut dp: HashMap<usize, Vec<i32>> = HashMap::new();
fn dfs(nums: &[i32], i: usize, dp: &mut HashMap<usize, Vec<i32>>) -> Vec<i32> {
dp.get(&i).cloned().unwrap_or_else(|| {
let x = nums.get(i.wrapping_sub(1)).copied().unwrap_or(1);
let largest_seq = (i..nums.len())
.filter(|&j| nums[j] % x == 0)
.map(|j| {
let mut next = vec![nums[j]];
next.extend(dfs(nums, j + 1, dp));
next
})
.max_by_key(|seq| seq.len())
.unwrap_or_else(Vec::new);
dp.insert(i, largest_seq.clone());
largest_seq
})
}
dfs(&nums, 0, &mut dp)
}
279. Perfect Squares medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/499
Min square numbers sum up to n
.
By wrong intuition would be just subtract maximum possible square number: 12 = 9 + remainder. So, we should explore all of possible squares and choose min count of them. We can do DFS and cache the result. To pass the TLE, we need to rewrite it back into bottom up DP.
Let’s write as shorter as we can by using:
minOf
, sqrt
without Math
, toFloat
vs toDouble
(1..)
x = 0
to safely invoke minOf
and unwrap
Time complexity: \(O(nsqrt(n))\)
Space complexity: \(O(n)\)
fun numSquares(n: Int): Int {
val dp = IntArray(n + 1)
for (x in 1..n)
dp[x] = (1..sqrt(x.toFloat()).toInt())
.minOf { 1 + dp[x - it * it] }
return dp[n]
}
pub fn num_squares(n: i32) -> i32 {
let mut dp = vec![0; n as usize + 1];
for x in 1..=n as usize {
dp[x] = (1..).take_while(|&k| k * k <= x)
.map(|k| 1 + dp[x - k * k]).min().unwrap();
}
dp[n as usize]
}
451. Sort Characters By Frequency medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/498
Sort string by char’s frequencies.
The optimal solution would be to sort [128]
size array of frequencies, then build a string in O(n). There are some other ways, however…
Let’s explore the shortest versions of code by using the API:
Time complexity: \(O(n)\), or O(nlog(n)) for sorting the whole string
Space complexity: \(O(n)\)
fun frequencySort(s: String) = s
.groupBy { it }.values
.sortedBy { -it.size }
.flatMap { it }
.joinToString("")
pub fn frequency_sort(s: String) -> String {
let mut f = vec![0; 128];
for b in s.bytes() { f[b as usize] += 1 }
let mut cs: Vec<_> = s.chars().collect();
cs.sort_unstable_by_key(|&c| (-f[c as usize], c));
cs.iter().collect()
}
49. Group Anagrams medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/497
Group words by chars in them.
We can use char’s frequencies or just sorted words as keys to grouping.
Use the standard API for Kotlin and Rust:
Time complexity: \(O(mn)\), for counting, mlog(n) for sorting
Space complexity: \(O(mn)\)
fun groupAnagrams(strs: Array<String>): List<List<String>> =
strs.groupBy { it.groupBy { it } }.values.toList()
pub fn group_anagrams(strs: Vec<String>) -> Vec<Vec<String>> {
let mut groups = HashMap::new();
for s in strs {
let mut key: Vec<_> = s.bytes().collect();
key.sort_unstable();
groups.entry(key).or_insert_with(Vec::new).push(s);
}
groups.into_values().collect()
}
387. First Unique Character in a String easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/496
First non-repeating char position.
Compute char’s frequencies, then find first of 1.
Let’s try to make code shorter: Kotlin:
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun firstUniqChar(s: String) = s.groupBy { it }
.run { s.indexOfFirst { this[it]!!.size < 2 } }
pub fn first_uniq_char(s: String) -> i32 {
let mut f = vec![0; 128];
for b in s.bytes() { f[b as usize] += 1 }
s.find(|c| f[c as usize] < 2).map_or(-1, |i| i as i32)
}
76. Minimum Window Substring hard blog post substack youtube
https://t.me/leetcode_daily_unstoppable/495
Minimum window of s including all chars of t.
The greedy approach with sliding window would work: move right window pointer right until all chars are obtained. Then move left border until condition no longer met.
There is an optimization possible: remove the need to check all character’s frequencies by counting how many chars are absent.
Let’s try to shorten the code:
.drop.take
is shorter than substring
, as skipping one if
into
shortern than to_string
Time complexity: \(O(n + m)\)
Space complexity: \(O(1)\)
fun minWindow(s: String, t: String): String {
val freq = IntArray(128)
for (c in t) freq[c.code]++
var i = 0
var r = arrayOf(s.length, s.length + 1)
var count = t.length
for ((j, c) in s.withIndex()) {
if (freq[c.code]-- > 0) count--
while (count == 0) {
if (j - i + 1 < r[1]) r = arrayOf(i, j - i + 1)
if (freq[s[i++].code]++ == 0) count++
}
}
return s.drop(r[0]).take(r[1])
}
pub fn min_window(s: String, t: String) -> String {
let mut freq = vec![0; 128];
for b in t.bytes() { freq[b as usize] += 1; }
let (mut i, mut r, mut c) = (0, 0..0, t.len());
for (j, b) in s.bytes().enumerate() {
if freq[b as usize] > 0 { c -= 1; }
freq[b as usize] -= 1;
while c == 0 {
if j - i + 1 < r.len() || r.len() == 0 { r = i..j + 1; }
let a = s.as_bytes()[i] as usize;
freq[a] += 1; if freq[a] > 0 { c += 1; }
i += 1;
}
}
s[r].into()
}
1043. Partition Array for Maximum Sum medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/493
Max sum of partition array into chunks size of at most k filled with max value in chunk.
Let’s just brute force with Depth-First Search starting from each i
position: search for the end of chunk j
and choose the maximum of the sum. max_sum[i] = optimal_chunk + max_sum[chunk_len]. This can be cached by the i
.
Then rewrite into bottom up DP.
k
Time complexity: \(O(n^2)\)
Space complexity: \(O(n)\)
fun maxSumAfterPartitioning(arr: IntArray, k: Int): Int {
val dp = IntArray(arr.size + 1)
for (i in arr.indices) {
var max = 0
for (j in i downTo max(0, i - k + 1)) {
max = max(max, arr[j])
dp[i + 1] = max(dp[i + 1], (i - j + 1) * max + dp[j])
}
}
return dp[arr.size]
}
pub fn max_sum_after_partitioning(arr: Vec<i32>, k: i32) -> i32 {
let mut dp = vec![0; arr.len() + 1];
for i in 0..arr.len() {
let mut max_v = 0;
for j in (0..=i).rev().take(k as usize) {
max_v = max_v.max(arr[j]);
dp[i + 1] = dp[i + 1].max((i - j + 1) as i32 * max_v + dp[j]);
}
}
dp[arr.len()]
}
1291. Sequential Digits medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/492
Numbers with sequential digits in low..high range.
Let’s write down all of them:
// 1 2 3 4 5 6 7 8 9
// 12 23 34 45 57 67 78 89
// 123 234 345 456 678 789
// 1234 2345 3456 4567 5678 6789
// 12345 23456 34567 45678 56789
// 123456 234567 345678 456789
// 1234567 2345678 3456789
// 12345678 23456789
// 123456789
After that you will get the intuition how they are built: we scan pairs, increasing first ten times and appending last digit of the second.
Let’s try to leverage the standard iterators in Kotlin & Rust:
Time complexity: \(O(1)\)
Space complexity: \(O(1)\)
fun sequentialDigits(low: Int, high: Int) =
(1..9).runningFold((1..9).toList()) { r, _ ->
r.windowed(2) { it[0] * 10 + it[1] % 10 }
}.flatten().filter { it in low..high }
pub fn sequential_digits(low: i32, high: i32) -> Vec<i32> {
(1..10).scan((1..10).collect::<Vec<_>>(), |s, _| {
let r = Some(s.clone());
*s = s.windows(2).map(|w| w[0] * 10 + w[1] % 10).collect(); r
}).flatten().filter(|&x| low <= x && x <= high).collect()
}
2966. Divide Array Into Arrays With Max Difference medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/491
Split array into tripples with at most k difference.
Sort, then just check k
condition.
Let’s use iterators in Kotlin and Rust:
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun divideArray(nums: IntArray, k: Int) = nums
.sorted().chunked(3).toTypedArray()
.takeIf { it.all { it[2] - it[0] <= k } } ?: arrayOf()
pub fn divide_array(mut nums: Vec<i32>, k: i32) -> Vec<Vec<i32>> {
nums.sort_unstable();
if nums.chunks(3).any(|c| c[2] - c[0] > k) { vec![] }
else { nums.chunks(3).map(|c| c.to_vec()).collect() }
}
739. Daily Temperatures medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/489
Array of distances to the next largest.
Let’s walk array backwards and observe which numbers we need to keep track of and which are irrelevant:
0 1 2 3 4 5 6 7
73 74 75 71 69 72 76 73
73 73 7
76 76 6
72 76 72 6 5 6 - 5 = 1
69 76 72 69 6 5 4
71 76 72 71 6 5 3 5 - 3 = 2
As we see, we must keep the increasing orders of values and drop each less than current. This technique is a known pattern called Monotonic Stack.
There are several ways to write that, let’s try to be brief.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun dailyTemperatures(temps: IntArray): IntArray =
Stack<Int>().run {
temps.indices.reversed().map { i ->
while (size > 0 && temps[peek()] <= temps[i]) pop()
(if (size > 0) peek() - i else 0).also { push(i) }
}.reversed().toIntArray()
}
pub fn daily_temperatures(temps: Vec<i32>) -> Vec<i32> {
let (mut r, mut s) = (vec![0; temps.len()], vec![]);
for (i, &t) in temps.iter().enumerate().rev() {
while s.last().map_or(false, |&j| temps[j] <= t) { s.pop(); }
r[i] = (*s.last().unwrap_or(&i) - i) as i32;
s.push(i);
}
r
}
150. Evaluate Reverse Polish Notation medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/488
Solve Reverse Polish Notation.
Push to stack until operation met, then pop twice and do op.
Let’s try to be brief.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun evalRPN(tokens: Array<String>) = Stack<Int>().run {
for (s in tokens) push(when (s) {
"+" -> pop() + pop()
"-" -> -pop() + pop()
"*" -> pop() * pop()
"/" -> pop().let { pop() / it }
else -> s.toInt()
})
pop()
}
pub fn eval_rpn(tokens: Vec<String>) -> i32 {
let mut s = vec![];
for t in tokens { if let Ok(n) = t.parse() { s.push(n) }
else { let (a, b) = (s.pop().unwrap(), s.pop().unwrap());
s.push(match t.as_str() {
"+" => a + b, "-" => b - a, "*" => a * b, _ => b / a }) }}
s[0]
}
232. Implement Queue using Stacks easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/487
Queue by 2 stacks.
Let’s write down how the numbers are added:
stack a: [1 2]
stack b: []
peek:
a: [1]
b: [2]
a: []
b: [2 1], b.peek == 1
Let’s do some code golf.
Time complexity: \(O(1)\) for total operations. In general, stack drain is a rare operation
Space complexity: \(O(n)\) for total operations.
class MyQueue() {
val a = Stack<Int>()
val b = Stack<Int>()
fun push(x: Int) = a.push(x)
fun pop() = peek().also { b.pop() }
fun peek(): Int {
if (b.size < 1) while (a.size > 0) b += a.pop()
return b.peek()
}
fun empty() = a.size + b.size == 0
}
struct MyQueue(Vec<i32>, Vec<i32>);
impl MyQueue {
fn new() -> Self { Self(vec![], vec![]) }
fn push(&mut self, x: i32) { self.0.push(x); }
fn pop(&mut self) -> i32 { self.peek(); self.1.pop().unwrap() }
fn peek(&mut self) -> i32 {
if self.1.is_empty() { self.1.extend(self.0.drain(..).rev()); }
*self.1.last().unwrap()
}
fn empty(&self) -> bool { self.0.len() + self.1.len() == 0 }
}
1074. Number of Submatrices That Sum to Target hard blog post substack youtube
https://t.me/leetcode_daily_unstoppable/486
Count submatrix target sums.
Precompute prefix sums, then calculate submatrix sum in O(1).
if
sTime complexity: \(O(n^4)\)
Space complexity: \(O(n^2)\)
fun numSubmatrixSumTarget(matrix: Array<IntArray>, target: Int): Int {
val s = Array(matrix.size + 1) { IntArray(matrix[0].size + 1) }
return (1..<s.size).sumOf { y -> (1..<s[0].size).sumOf { x ->
s[y][x] = matrix[y - 1][x - 1] + s[y - 1][x] + s[y][x - 1] - s[y - 1][x - 1]
(0..<y).sumOf { y1 -> (0..<x).count { x1 ->
target == s[y][x] - s[y1][x] - s[y][x1] + s[y1][x1]
}}
}}
}
pub fn num_submatrix_sum_target(matrix: Vec<Vec<i32>>, target: i32) -> i32 {
let mut s = vec![vec![0; matrix[0].len() + 1]; matrix.len() + 1];
(1..s.len()).map(|y| (1..s[0].len()).map(|x| {
s[y][x] = matrix[y - 1][x - 1] + s[y - 1][x] + s[y][x - 1] - s[y - 1][x - 1];
(0..y).map(|y1| (0..x).filter_map(|x1|
if target == s[y][x] - s[y1][x] - s[y][x1] + s[y1][x1] { Some(1) } else { None }
).count() as i32).sum::<i32>()
}).sum::<i32>()).sum()
}
629. K Inverse Pairs Array hard blog post substack youtube
https://t.me/leetcode_daily_unstoppable/485
Number of arrays of 1..n with k reversed order pairs.
First step: write down all the arrays for some example n
, for every possible k
:
// 1 2 3 4
// f(4,1) = 3
// 1 3 2 4 [. . . 32 . . ] 3 f(3, 1) = 2 = 1 + f(2, 1)
// 1 2 4 3 [43 . . . . . ] 4 f(2, 1) = 1
// 2 1 3 4 [. . . . . 21] 2
// f(4, 2) = 5
// 1 3 4 2 [. 42 . 32 . . ] 4 f(4, 2) = 1 + f(3, 1) + f(3, 2) = 1 + sum_j_k(f(2, j))
// 1 4 2 3 [43 42 . . . . ] 4 f(3, 2) = 1 + f(2, 1) = 2
// 2 1 4 3 [43 . . . . 21] 4 f(2, 2) = 0
// 2 3 1 4 [. . . . 31 21] 3 f(3, 2) = 2
// 3 1 2 4 [. . . 32 31 . ] 3
// f(4, 3) = 6
// 1 4 3 2 [43 42 . 32 . . ] 4 f(4, 3) = 1 + f(3, 1) + f(3, 2) + f(3, 3)
// 2 3 4 1 [. . 41 . 31 21] 4
// 2 4 1 3 [43 . 41 . . 21] 4
// 3 1 4 2 [. 42 . 32 31 . ] 4
// 3 2 1 4 [. . . 32 31 21] 3 f(3, 3) = 1
// 4 1 2 3 [43 42 41 . . . ] 4
// f(4, 4) = 5
// 2 4 3 1 [43 . 41 . 31 21] 4
// 3 2 4 1 [. . 41 32 31 21] 4 f(4, 4) = f(3, 1) + f(3, 2) + f(3, 3) + f(3, 4)
// 3 4 1 2 [. 42 41 32 31 . ] 4 f(3, 4) = 0
// 4 1 3 2 [43 42 41 32 . . ] 4
// 4 2 1 3 [43 42 41 . . 21] 4
// f(4, 5) = 3
// 3 4 2 1 [. 42 41 32 31 21] 4 f(4, 5) = f(3, 2) + f(3, 3)
// 4 3 1 2 [43 42 41 32 31 . ] 4
// 4 2 3 1 [43 42 41 . 31 21] 4
// f(4, 6) = 1
// 4 3 2 1 [43 42 41 32 31 21] 4 f(4, 6) = f(3, 3) = 1
// f(5, 10) = 1
// f(5, x) = 1, x = 6 + 4 = 10, f(5, 10) = 1, f(5, 9) = f(4, 6) + f(4, 5) = 1+3=4
// f(6, 15) = 1 f(5, 8) = f(4, 6) + f(4, 5) + f(4, 4) = 1+3+5=9
// f(7, 21) = 1 f(5, 7) = f(5, 8) + f(4, 3) = 9+6=15
// f(8, 28) = 1 f(5, 6) = f(5, 7) + f(4, 2) = 15+5 =20
// f(5, 5) = f(5, 6) + f(4, 1) = 20+3=23--->22
// f(5, 4) = f(5, 5) + 1 = 24--->20
// f(5, 3) = 1 + f(4,1)+f(4,2)+f(4,3) = 1+3+5+6=15
// f(5, 2) = 1 + f(4,1) + f(4, 2) = 1+3+5 = 9
// f(5, 1) = 1 + f(4, 1)= 1+3=4
// f(5, 0) = 1
// f(0) = 0
// f(1) = 1
// f(2) = 1 1
// f(3) = 1 2 2 1
// 0 1 2 3 4 5 6
//
// 1 2 2 1
// 1 2 2 1
// 1 3 5 6 5 3 1
// f(4) = 1 3 5 (6) 5 3 1 1=0+1,3=1+2,5=3+2,6=5+1,5=6-1,3=5-2,1=3-2,0=1-1
// + 1 3 4 6 5 3 1
// - 1 3 5 6 5 3
// 0 1 2 3 4 5 6 7 8 9 10
//
// 1 3 5 6 5 3 1
// 1 3 5 6 5 3 1
// 1 4 9 15 20 22 20 15 9 4 1
// 0 1 2 3 4 5 6 7 8 9 10
// 5 = 10 - (7 - 2)
// f(5) = 1 4 9 15 (20 22 20) 15 9 4 1 20 = 15+5, 22 = 20+3-1, 20=22+1-3, 15=20-5, 9=15-6, 4=9-5, 1=4-3
// f(6) = 1 5 14 28 48 70 90 105 ??? 10590 70 48 28 14 5 1
// f(7) = 1 6 20 48
// f(6, 15) = 1
// f(9, 36) = 1 f(6, 14) = f(5, 10) + f(5, 9) = 1+4 = 5
// f(6, 13) = f(5, 10) + f(5, 9) + f(5, 8) = 5+9=14
// f(6, 12) =
// [15..]+
// [..15]-
// [ 21 ]
After several hours (3 in my case) of staring at those numbers the idea should came to your mind: there is a pattern.
For every n
, if all the numbers are reversed, then there are exactly Fibonacci(n)
reversed pairs:
// f(5, x) = 1, x = 6 + 4 = 10, f(5, 10) = 1
// f(6, 15) = 1
// f(7, 21) = 1
// f(8, 28) = 1
Another pattern is how we make a move in n
space:
f(3, 1) = 2 = 1 + f(2, 1)
f(4, 2) = 1 + f(3, 1) + f(3, 2) = 1 + sum_j_k(f(2, j))
f(3, 2) = 1 + f(2, 1) = 2
f(4, 3) = 1 + f(3, 1) + f(3, 2) + f(3, 3)
f(4, 4) = f(3, 1) + f(3, 2) + f(3, 3) + f(3, 4)
It almost works, until it not: at some point pattern breaks, so search what is it.
Let’s write all the k
numbers for each n
:
f(0) = 0
f(1) = 1
f(2) = 1 1
f(3) = 1 2 2 1
f(4) = 1 3 5 6 5 3 1
f(5) = 1 4 9 15 20 22 20 15 9 4 1
There is a symmetry and we can deduce it by intuition: add the previous and at some point start to remove:
// 1 2 2 1
// 1 2 2 1
// 1 3 5 6 5 3 1
// 1 3 5 6 5 3 1
// 1 3 5 6 5 3 1
// 1 4 9 15 20 22 20 15 9 4 1
Now, the picture is clear. At some index we must start to remove the previous sequence.
We are not finished yet, however: solution will give TLE. Fibonacci became too big. So, another hint: numbers after k
doesn’t matter.
This is a filter problem: it filters you.
k
numbersTime complexity: \(O(nk)\)
Space complexity: \(O(k)\)
fun kInversePairs(n: Int, k: Int): Int {
var fib = 1
var prev = LongArray(k + 1).apply { this[0] = 1 }
var curr = LongArray(k + 1)
repeat(n) {
fib = fib + it
var c = 0L
for (x in 0..k) {
if (x < fib - it) c += prev[x]
if (x - it > 0) c -= prev[x - it - 1]
curr[x] = (c + 1_000_000_007L) % 1_000_000_007L
}
prev = curr.also { curr = prev }
}
return if (k >= fib) 0 else prev[k].toInt()
}
pub fn k_inverse_pairs(n: i32, k: i32) -> i32 {
let mut fib = 1;
let mut prev = vec![1; (k + 1) as usize];
let mut curr = vec![1; (k + 1) as usize];
for i in 0..n {
fib = fib + i;
let mut c = 0i64;
for x in 0..=k {
if x < fib - i { c += prev[x as usize]; }
if x - i > 0 { c -= prev[(x - i - 1) as usize]; }
curr[x as usize] = (c + 1_000_000_007) % 1_000_000_007;
}
std::mem::swap(&mut prev, &mut curr);
}
if k >= fib { 0 } else { prev[k as usize] as i32 }
}
576. Out of Boundary Paths medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/484
Number of paths from cell in grid to out of boundary.
Let’s do a Brute-Force Depth-First Search from the current cell to neighbors. If we are out of boundary, we have a 1
path, and 0
if moves are out. Then add memoization with a HashMap.
long
helps to shorten the codeTime complexity: \(O(nmv)\)
Space complexity: \(O(nmv)\)
fun findPaths(m: Int, n: Int, maxMove: Int, startRow: Int, startColumn: Int): Int {
val dp = mutableMapOf<Pair<Pair<Int, Int>, Int>, Long>()
fun dfs(y: Int, x: Int, move: Int): Long = dp.getOrPut(y to x to move) {
if (y < 0 || x < 0 || y == m || x == n) 1L
else if (move <= 0) 0L else
dfs(y - 1, x, move - 1) +
dfs(y + 1, x, move - 1) +
dfs(y, x - 1, move - 1) +
dfs(y, x + 1, move - 1) } % 1_000_000_007L
return dfs(startRow, startColumn, maxMove).toInt()
}
pub fn find_paths(m: i32, n: i32, max_move: i32, start_row: i32, start_column: i32) -> i32 {
let mut dp = HashMap::new();
fn dfs( y: i32, x: i32, mov: i32, m: i32, n: i32, dp: &mut HashMap<(i32, i32, i32), i64> ) -> i64 {
if y < 0 || x < 0 || y == m || x == n { 1 } else if mov<= 0 { 0 } else {
if let Some(&cache) = dp.get(&(y, x, mov)) { cache } else {
let result = (dfs(y - 1, x, mov - 1, m, n, dp) +
dfs(y + 1, x, mov - 1, m, n, dp) +
dfs(y, x - 1, mov - 1, m, n, dp) +
dfs(y, x + 1, mov - 1, m, n, dp)) % 1_000_000_007;
dp.insert((y, x, mov), result); result
}
}
}
dfs(start_row, start_column, max_move, m, n, &mut dp) as i32
}
1143. Longest Common Subsequence medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/483
Longest common subsequence of two strings.
We can start from a brute force solution: given the current positions i
and j
we take them into common if text1[i] == text2[j]
or choose between taking from text1[i]
and text2[j]
if not. The result will only depend on the current positions, so can be cached. From this, we can rewrite the solution to iterative version.
len + 1
dp size to avoid boundary checksdp[0][0]
must be the out of boundary valuefold
can save us some lines of codeTime complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\)
fun longestCommonSubsequence(text1: String, text2: String): Int {
val dp = Array(text1.length + 1) { IntArray(text2.length + 1) }
for (i in text1.lastIndex downTo 0)
for (j in text2.lastIndex downTo 0)
dp[i][j] = if (text1[i] == text2[j])
1 + dp[i + 1][j + 1] else
max(dp[i + 1][j], dp[i][j + 1])
return dp[0][0]
}
pub fn longest_common_subsequence(text1: String, text2: String) -> i32 {
let mut dp = vec![vec![0; text2.len() + 1]; text1.len() + 1];
text1.bytes().enumerate().fold(0, |_, (i, a)|
text2.bytes().enumerate().fold(0, |r, (j, b)| {
let l = if a == b { 1 + dp[i][j] } else { dp[i][j + 1].max(r) };
dp[i + 1][j + 1] = l; l
})
)
}
1457. Pseudo-Palindromic Paths in a Binary Tree medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/482
Count can-form-a-palindrome paths root-leaf in a binary tree.
Let’s walk a binary tree with Depth-First Search and check the frequencies in path’s numbers. To form a palindrome, only a single frequency can be odd.
Time complexity: \(O(n)\)
Space complexity: \(O(log(n))\)
fun pseudoPalindromicPaths (root: TreeNode?): Int {
fun dfs(n: TreeNode?, freq: Int): Int = n?.run {
val f = freq xor (1 shl `val`)
if (left == null && right == null) {
if (f and (f - 1) == 0) 1 else 0
} else dfs(left, f) + dfs(right, f)
} ?: 0
return dfs(root, 0)
}
pub fn pseudo_palindromic_paths (root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
fn dfs(n: &Option<Rc<RefCell<TreeNode>>>, freq: i32) -> i32 {
n.as_ref().map_or(0, |n| {
let n = n.borrow();
let f = freq ^ (1 << n.val);
dfs(&n.left, f) + dfs(&n.right, f) +
(n.left.is_none() && n.right.is_none() && (f & (f - 1) == 0)) as i32
})
}
dfs(&root, 0)
}
1239. Maximum Length of a Concatenated String with Unique Characters medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/481
Max length subsequence of strings array with unique chars.
Let’s do a brute-force Depth-First Search and keep track of used chars so far.
mask xor word
must not be equal mask or word
for them not to intersectTime complexity: \(O(2^n)\)
Space complexity: \(O(n)\)
fun maxLength(arr: List<String>): Int {
val sets = arr.filter { it.toSet().size == it.length }
fun dfs(i: Int, s: Set<Char>): Int = if (i == sets.size) 0
else max(
if (sets[i].any { it in s }) 0 else
sets[i].length + dfs(i + 1, s + sets[i].toSet()),
dfs(i + 1, s)
)
return dfs(0, setOf())
}
pub fn max_length(arr: Vec<String>) -> i32 {
let bits: Vec<_> = arr.into_iter()
.filter(|s| s.len() == s.chars().collect::<HashSet<_>>().len())
.map(|s| s.bytes().fold(0, |m, c| m | 1 << (c - b'a')))
.collect();
fn dfs(bits: &[i32], i: usize, mask: i32) -> i32 {
if i == bits.len() { 0 } else {
dfs(bits, i + 1, mask).max(
if (bits[i] | mask != bits[i] ^ mask) { 0 } else
{ bits[i].count_ones() as i32 + dfs(bits, i + 1, mask | bits[i]) }
)}
}
dfs(&bits, 0, 0)
}
645. Set Mismatch easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/480
Return missing and duplicated number in 1..n array with one number replaced.
First try to find a xor
solution by observing xor differencies. Then give up and just compare sorted order or even better HashSet with expected.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun findErrorNums(nums: IntArray) = with(nums) {
val missing = ((1..size) - toSet()).first()
val delta = sum() - (size + 1) * size / 2
intArrayOf(missing + delta, missing)
}
pub fn find_error_nums(nums: Vec<i32>) -> Vec<i32> {
let sz = nums.len() as i32;
let sum: i32 = nums.iter().sum();
let set_sum: i32 = nums.into_iter().collect::<HashSet<_>>().iter().sum();
vec![sum - set_sum, sz * (sz + 1) / 2 - set_sum]
}
198. House Robber medium blog post substack youtube
https://youtu.be/UeejjxR-skM
https://t.me/leetcode_daily_unstoppable/479
Max sum to rob non adjacent items in array.
Let’s inspect how robber acts by scanning array home by home:
// 2 7 9 3 1
// 2 max(2) = 2
// 7 max(7, 2) = 7
// b a 9 max(9 + b, a) = 11
// b a 3 max(3 + b, a) = 11
// b a 1 max(1 + b, a) = 12
We see that he can choose to take the current home and drop the previous, or keep the previous. Only the two last sums matter.
fold
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun rob(nums: IntArray) =
nums.fold(0 to 0) { (a, b), x -> max(x + b, a) to a }.first
pub fn rob(nums: Vec<i32>) -> i32 {
nums.iter().fold((0, 0), |(a, b), &x| (b, b.max(a + x))).1
}
907. Sum of Subarray Minimums medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/478
Sum of minimums of all array ranges.
To build an intuition, we must write some examples where numbers will increase and decrease.
Next, write down all the subarrays and see how the result differs when we add another number.
Let g[i]
be minimums for all subarrays [i..]
. Result for all subarrays is f[i] = g[i] + f[i + 1]
.
Now, let’s find how g
can be split into subproblems:
// 5 2 1
// 0 1 2 3 4 5 6 7 8 910111213
// g(5 2 3 5 4 3 2 1 3 2 3 2 1 4) = 5 + g(2 3 5 4 3 2 1 3 2 3 2 1 4)
// 2 1
// g(2 3 5 4 3 2 1 3 2 3 2 1 4) = 2 + g(2 2 2 2) + g(2 1 3 2 3 2 1 4)
// 3 2 1
// g(3 5 4 3 2 1 3 2 3 2 1 4) = 3 + g(3 3) + g(3 2 1 3 2 3 2 1 4)
// 5 4 3 2 1
// g(5 4 3 2 1 3 2 3 2 1 4) = 5 + g(4 3 2 1 3 2 3 2 1 4)
// 4 3 2 1
// g(4 3 2 1 3 2 3 2 1 4) = 4 + g(3 2 1 3 2 3 2 1 4)
// 3 2 1
// g(3 2 1 3 2 3 2 1 4) = 3 + g(2 1 3 2 3 2 1 4)
// 2 1
// g(2 1 3 2 3 2 1 4) = 2 + g(1 3 2 3 2 1 4)
Notice the pattern: if next value (right to left) is bigger, we just reuse previous g, but if it is smaller, we need to find closest positions and replace all the numbers to arr[i]
.
To do this step in O(1) there is a known Increasing Stack technique: put values that bigger and each smaller value will discard all larger numbers.
size
to store absent value and safely access g[j]
fold
to reduce some lines of codeTime complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun sumSubarrayMins(arr: IntArray) = with(Stack<Int>()) {
val g = IntArray(arr.size + 1)
(arr.lastIndex downTo 0).fold(0) { prev, i ->
while (isNotEmpty() && arr[peek()] >= arr[i]) pop()
val j = if (isEmpty()) arr.size else peek()
g[i] = (j - i) * arr[i] + g[j]
push(i)
(prev + g[i]) % 1_000_000_007
}
}
pub fn sum_subarray_mins(arr: Vec<i32>) -> i32 {
let (mut s, mut g) = (Vec::new(), vec![0; arr.len() + 1]);
arr.iter().enumerate().rev().fold(0, |f, (i, &v)| {
while s.last().map_or(false, |&j| arr[j] >= v) { s.pop(); }
let j = *s.last().unwrap_or(&arr.len());
g[i] = (j - i) as i32 * v + g[j];
s.push(i);
(f + g[i]) % 1_000_000_007
})
}
931. Minimum Falling Path Sum medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/477
Min sum moving bottom center, left, right in 2D matrix.
At every cell we must add it value to path plus min of a three direct top cells as they are the only way here.
We can reuse the matrix or better use separate temporal array.
Time complexity: \(O(mn)\)
Space complexity: \(O(1)\), or O(m) to not corrupt the inputs
fun minFallingPathSum(matrix: Array<IntArray>): Int {
for (y in 1..<matrix.size) for (x in 0..<matrix[0].size)
matrix[y][x] += (max(0, x - 1)..min(x + 1, matrix[0].lastIndex))
.minOf { matrix[y - 1][it] }
return matrix.last().min()
}
pub fn min_falling_path_sum(matrix: Vec<Vec<i32>>) -> i32 {
*matrix.into_iter().reduce(|dp, row|
row.iter().enumerate().map(|(x, &v)|
v + dp[x.max(1) - 1..=(x + 1).min(dp.len() - 1)]
.iter().min().unwrap()
).collect()
).unwrap().iter().min().unwrap()
}
70. Climbing Stairs easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/476
Ways to climb n stairs by 1 or 2 steps.
Start with brute force DFS search: either go one or two steps and cache the result in a HashMap<Int, Int>. Then convert solution to iterative version, as only two previous values matter.
if n < 4
also
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun climbStairs(n: Int): Int {
var p = 0
var c = 1
for (i in 1..n) c += p.also { p = c }
return c
}
pub fn climb_stairs(n: i32) -> i32 {
(0..n).fold((0, 1), |(p, c), _| (c, p + c)).1
}
1207. Unique Number of Occurrences easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/474
Are array frequencies unique.
Just count frequencies.
Let’s use some Kotlin’s API:
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun uniqueOccurrences(arr: IntArray) =
arr.asList().groupingBy { it }.eachCount().values.run {
toSet().size == size
}
pub fn unique_occurrences(arr: Vec<i32>) -> bool {
let occ = arr.iter().fold(HashMap::new(), |mut m, &x| {
*m.entry(x).or_insert(0) += 1; m
});
occ.len() == occ.values().collect::<HashSet<_>>().len()
}
380. Insert Delete GetRandom O(1) medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/473
Implement HashSet
There is a random
method exists in Kotlin’s MutableSet
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/random.html.
However, let’s just use array to store values and save positions in a HashMap
. The order in array didn’t matter, so we can remove elements in O(1).
To save some symbols of code, we can extend from ArrayList.
Time complexity: \(O(1)\), per operation
Space complexity: \(O(1)\), per operation
class RandomizedSet(): ArrayList<Int>() {
val vToPos = HashMap<Int, Int>()
fun insert(v: Int): Boolean {
if (vToPos.contains(v)) return false
add(v)
vToPos[v] = lastIndex
return true
}
override fun remove(v: Int): Boolean {
val pos = vToPos.remove(v) ?: return false
set(pos, last())
if (last() != v) vToPos[last()] = pos
removeLast()
return true
}
fun getRandom() = random()
}
use rand::{thread_rng, Rng};
use std::collections::HashMap;
struct RandomizedSet {
vec: Vec<i32>,
v_to_i: HashMap<i32, usize>,
}
impl RandomizedSet {
fn new() -> Self {
Self { vec: vec![], v_to_i: HashMap::new() }
}
fn insert(&mut self, v: i32) -> bool {
if self.v_to_i.entry(v).or_insert(self.vec.len()) != &self.vec.len() {
return false;
}
self.vec.push(v);
true
}
fn remove(&mut self, v: i32) -> bool {
self.v_to_i.remove(&v).map_or(false, |i| {
let last = self.vec.pop().unwrap();
if (last != v) {
self.vec[i] = last;
self.v_to_i.insert(last, i);
}
true
})
}
fn get_random(&self) -> i32 {
self.vec[thread_rng().gen_range(0, self.vec.len())]
}
}
2225. Find Players With Zero or One Losses medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/472
[sorted winners list, sorted single lose list]
No special algorithms here, just a set
manipulation.
Let’s use some Kotlin’s API:
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun findWinners(matches: Array<IntArray>) = buildList {
val winners = matches.map { it[0] }.toSet()
val losers = matches.groupingBy { it[1] }.eachCount()
add((winners - losers.keys).sorted())
add(losers.filter { (k, v) -> v == 1 }.keys.sorted())
}
1657. Determine if Two Strings Are Close medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/471
Are strings convertible by swapping existing chars positions or frequencies.
By the problem definition, we must compare the frequencies numbers. Also, sets of chars must be equal.
Let’s use some Kotlin’s API:
Time complexity: \(O(n)\), as we are sorting only 26 elements
Space complexity: \(O(n)\)
fun String.f() = groupingBy { it }.eachCount()
.run { keys to values.sorted() }
fun closeStrings(word1: String, word2: String) =
word1.f() == word2.f()
1347. Minimum Number of Steps to Make Two Strings Anagram medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/470
Min operations to make string t
anagram of s
.
Let’s compare char’s frequencies of those two strings.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun minSteps(s: String, t: String) =
IntArray(128).let {
for (c in s) it[c.toInt()]++
for (c in t) it[c.toInt()]--
it.sumOf { abs(it) } / 2
}
1704. Determine if String Halves Are Alike easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/469
https://t.me/leetcode_daily_unstoppable/469
Let’s use some Kotlin’s API:
Time complexity: \(O(n)\)
Space complexity:
\(O(n)\), can be O(1) with asSequence
val vw = "aeiouAEIOU".toSet()
fun halvesAreAlike(s: String) =
s.take(s.length / 2).count { it in vw } ==
s.drop(s.length / 2).count { it in vw }
1026. Maximum Difference Between Node and Ancestor medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/468
Max diff between node and ancestor in a binary tree.
Let’s traverse the tree with Depth-First Search and keep track of the max and min values.
Time complexity: \(O(n)\)
Space complexity: \(O(log(n))\)
fun maxAncestorDiff(root: TreeNode?): Int {
var res = 0
fun dfs(n: TreeNode?): List<Int> = n?.run {
(dfs(left) + dfs(right) + listOf(`val`)).run {
listOf(min(), max()).onEach { res = max(res, abs(`val` - it)) }
}
} ?: listOf()
dfs(root)
return res
}
2385. Amount of Time for Binary Tree to Be Infected medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/467
Max distance from node in a Binary Tree.
Let’s build a graph, then do a Breadth-First Search from starting node.
We can store it in a parent[TreeNode]
map or just in two directional node to list<node>
graph.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun amountOfTime(root: TreeNode?, start: Int): Int {
val fromTo = mutableMapOf<TreeNode, MutableList<TreeNode>>()
var queue = ArrayDeque<TreeNode>()
val visited = mutableSetOf<TreeNode>()
fun dfs(n: TreeNode): Unit = with (n) {
if (`val` == start) {
queue.add(n)
visited.add(n)
}
left?.let {
fromTo.getOrPut(n) { mutableListOf() } += it
fromTo.getOrPut(it) { mutableListOf() } += n
dfs(it)
}
right?.let {
fromTo.getOrPut(n) { mutableListOf() } += it
fromTo.getOrPut(it) { mutableListOf() } += n
dfs(it)
}
}
root?.let { dfs(it) }
var time = -1
while (queue.isNotEmpty()) {
repeat(queue.size) {
var x = queue.removeFirst()
fromTo[x]?.onEach {
if (visited.add(it)) queue.add(it)
}
}
time++
}
return time
}
872. Leaf-Similar Trees easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/466
Are leafs sequences equal for two trees.
Let’s build a leafs lists and compare them.
Let’s use recursive function.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun leafs(n: TreeNode?): List<Int> = n?.run {
(leafs(left) + leafs(right))
.takeIf { it.isNotEmpty() } ?: listOf(`val`)
} ?: listOf()
fun leafSimilar(root1: TreeNode?, root2: TreeNode?) =
leafs(root1) == leafs(root2)
938. Range Sum of BST easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/465
Sum of BST in range [low..high].
Let’s iterate it using a Depth-First Search and check if each value is in the range.
Time complexity: \(O(r)\), r is a range
Space complexity: \(O(log(n))\)
fun rangeSumBST(root: TreeNode?, low: Int, high: Int): Int =
root?.run {
(if (`val` in low..high) `val` else 0) +
(if (`val` > low) rangeSumBST(left, low, high) else 0) +
(if (`val` < high) rangeSumBST(right, low, high) else 0)
} ?: 0
446. Arithmetic Slices II - Subsequence hard blog post substack youtube
https://t.me/leetcode_daily_unstoppable/464
Count of arithmetic subsequences.
We can take every pair and search for the third element.
The result only depends on the diff
and suffix array position, so can be cached.
1
then add the suffix count. Wrong approach: just count the 1
at the end of the sequence.Time complexity:
\(O(n^2)\), it looks like n^4, but the dfs
n^2 part will only go deep once
Space complexity: \(O(n^2)\)
fun numberOfArithmeticSlices(nums: IntArray): Int {
val dp = mutableMapOf<Pair<Int, Int>, Int>()
fun dfs(i: Int, k: Int): Int = dp.getOrPut(i to k) {
var count = 0
for (j in i + 1..<nums.size)
if (nums[i].toLong() - nums[k] == nums[j].toLong() - nums[i])
count += 1 + dfs(j, i)
count
}
var count = 0
for (i in nums.indices)
for (j in i + 1..<nums.size)
count += dfs(j, i)
return count
}
1235. Maximum Profit in Job Scheduling hard blog post substack youtube
https://t.me/leetcode_daily_unstoppable/463
Max profit in non-intersecting jobs given startTime[], endTime[] and profit[].
Start with sorting jobs by the startTime
. Then let’s try to find a subproblem: consider the only last element - it has maximum profit in itself. Then, move one index left: now, if we take the element, we must drop all the intersected jobs. Given that logic, there is a Dynamic Programming recurrence: dp[i] = max(dp[i + 1], profit[i] + dp[next])
.
The tricky part is how to faster find the next
non-intersecting position: we can use the Binary Search
Try to solve the problem for examples, there are only several ways you could try: greedy or dp. After 1 hour, use the hints.
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun jobScheduling(startTime: IntArray, endTime: IntArray, profit: IntArray): Int {
val inds = startTime.indices.sortedBy { startTime[it] }
val dp = IntArray(inds.size + 1)
for (i in inds.indices.reversed()) {
var lo = i + 1
var hi = inds.lastIndex
while (lo <= hi) {
val m = lo + (hi - lo) / 2
if (endTime[inds[i]] > startTime[inds[m]]) lo = m + 1 else hi = m - 1
}
dp[i] = max(dp[i + 1], profit[inds[i]] + dp[lo])
}
return dp[0]
}
300. Longest Increasing Subsequence medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/462
Longest increasing subsequence length.
This is a classical problem that has the optimal algorithm that you must know https://en.wikipedia.org/wiki/Longest_increasing_subsequence.
For every new number, check its position in an increasing sequence by Binary Search:
increasing sequence
1 3 5 7 9 insert 6
^
1 3 5 6 9
As we do not care about the actual numbers, only the length, this would work. (To restore the actual subsequence, we must remember each predecessor, see the wiki)
If you didn’t remember how to restore the insertion point from binarySearch
(-i-1), better implement it yourself:
lo
and hi
lo = mid + 1
, hi = mid - 1
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun lengthOfLIS(nums: IntArray): Int {
val seq = mutableListOf<Int>()
for (x in nums)
if (seq.isEmpty()) seq += x else {
var i = seq.binarySearch(x)
if (i < 0) i = -i - 1
if (i == seq.size) seq += x else seq[i] = x
}
return seq.size
}
2870. Minimum Number of Operations to Make Array Empty medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/461
Minimum pairs or triples duplicate removal operations to empty array of numbers.
The first idea, is to count each kind of number. Then we must analyze each frequency
: the number of removal operations ops
will be the same for each f
, so we can write a Dynamic Programming recurrent formula: ops(f) = 1 + min(ops(f - 2), ops(f - 3))
. This is an accepted solution.
Then, we can think about other ways to optimally split f
into a sum of a*2 + b*3
: we must maximize b
and minimize a
. To do that, let’s prioritize f % 3 == 0
check. Our checks will be in this order:
f % 3 == 0 -> f / 3
(f - 2) % 3 == 0 -> 1 + f / 2
((f - 2) - 2) % 3 == 0 -> 1 + f / 2
... and so on
However, we can spot that recurrence repeat itself like this: f, f - 2, f - 4, f - 6, ...
. As 6
is also divisible by 3
, there are total three checks needed: f % 3, (f - 2) % 3 and (f - 4) % 3
.
Write the recurrent DFS function, then add a HashMap cache, then optimize everything out. Use the Kotlin’s API:
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun minOperations(nums: IntArray) = nums
.groupBy { it }.mapValues { it.value.size }.values
.sumOf { f -> when {
f < 2 -> return -1
f % 3 == 0 -> f / 3
(f - 2) % 3 == 0 || (f - 4) % 3 == 0 -> 1 + f / 3
else -> return -1
}}
2125. Number of Laser Beams in a Bank medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/460
Beams count between consequent non-empty row’s 1
s.
By the problem definition, count = sum_i_j(count_i * count_j)
Let’s use some Kotlin’s API:
Time complexity: \(O(nm)\)
Space complexity:
\(O(n)\), can be reduced to O(1) with asSequence
and fold
.
fun numberOfBeams(bank: Array<String>) =
bank.map { it.count { it == '1' } }
.filter { it > 0 }
.windowed(2)
.map { (a, b) -> a * b }
.sum() ?: 0
2610. Convert an Array Into a 2D Array With Conditions medium blog post substack youtube ![image.png](https://assets.leetcode.com/users/images/78cf9bd1-967d-4de2-9948-c311f56960b1_1704183026.395581.png
https://t.me/leetcode_daily_unstoppable/459
Convert numbers array into array of unique number-rows.
Let’s count each kind of number, then use each unique number to build the rows.
Kotlin’s API can be helpful:
Time complexity:
\(O(uf)\) where, u - number of uniq elements, f - max frequency. Worst case O(n^2): 1 2 3 4 1 1 1 1
, u = n / 2, f = n / 2. This can be improved to O(n) by removing the empty collections from freq
.
Space complexity: \(O(n)\)
fun findMatrix(nums: IntArray): List<List<Int>> {
val freq = nums.groupBy { it }
.mapValues { it.value.toMutableList() }
return buildList {
repeat(freq.values.maxOf { it.size }) {
add(buildList {
for ((k, v) in freq)
if (v.isNotEmpty()) add(v.removeLast())
})
}
}
}
455. Assign Cookies easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/458
Max count of greedy children g[Int] to assign cookies with sizes s[Int].
The optimal way to assign cookies is to start with less greed. We can put cookies and children in two PriorityQueues or just sort two arrays and maintain two pointers.
g=[1] s=[1]
, g=[2] s=[1]
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(1)\)
fun findContentChildren(g: IntArray, s: IntArray): Int {
g.sort()
s.sort()
var j = 0
return g.count {
while (j < s.size && s[j] < it ) j++
j++ < s.size
}
}
1624. Largest Substring Between Two Equal Characters easy blog post substack youtube https://youtu.be/BF4M70PncfE
https://t.me/leetcode_daily_unstoppable/456
Max distance between same chars in string.
We must remember the first occurrence position of each kind of character.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun maxLengthBetweenEqualCharacters(s: String) =
with(mutableMapOf<Char, Int>()) {
s.indices.maxOf { it - 1 - getOrPut(s[it]) { it } }
}
1897. Redistribute Characters to Make All Strings Equal easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/455
Is it possible to split all the words[] characters into words.size groups.
To understand the problem, consider example: a abc abbcc
-> [abc] [abc] [abc]
. We know the result words count, and we know the count of each kind of character. So, just make sure, every character’s count can be separated into words.size
groups.
[aabc] [aabc] [aabc]
Time complexity: \(O(nw)\)
Space complexity: \(O(1)\)
fun makeEqual(words: Array<String>) =
IntArray(26).apply {
for (w in words) for (c in w) this[c.toInt() - 'a'.toInt()]++
}.all { it % words.size == 0 }
1335. Minimum Difficulty of a Job Schedule hard blog post substack youtube
https://t.me/leetcode_daily_unstoppable/454
Min sum of maximums jobDifficulty[i] per day d preserving the order
Let’s brute-force optimal interval of jobs jobInd..j
for every day using Depth-First Search. The result will only depend on the starting jobInd
and the current day
, so can be cached.
Time complexity:
\(O(dn^2)\), dn
for the recursion depth and another n
for the inner loop
Space complexity: \(O(dn)\)
fun minDifficulty(jobDifficulty: IntArray, d: Int): Int {
val dp = mutableMapOf<Pair<Int, Int>, Int>()
fun dfs(jobInd: Int, day: Int): Int = when {
jobInd == jobDifficulty.size -> if (day == d) 0 else Int.MAX_VALUE / 2
day == d -> Int.MAX_VALUE / 2
else -> dp.getOrPut(jobInd to day) {
var max = 0
(jobInd..jobDifficulty.lastIndex).minOf { i ->
max = max(max, jobDifficulty[i])
max + dfs(i + 1, day + 1)
}
}}
return dfs(0, 0).takeIf { it < Int.MAX_VALUE / 2 } ?: -1
}
1531. String Compression II hard blog post substack youtube
https://t.me/leetcode_daily_unstoppable/453
Min length of run-length encoded aabcc -> a2bc3 after deleting at most k characters
Let’s consider starting from every position, then we can split the problem: result[i] = some_function(i..j) + result[j].
The hardest part is to find an optimal j
position.
The wrong way: trying to count how many s[j]==s[i], and to keep them, removing all other chars s[j]!=s[i]. This didn’t give us the optimal solution for s[i..j], as we forced to keep s[0].
The correct way: keeping the most frequent char in s[i..j], removing all other chars.
Spend 1-2.5 hours max on the problem, then steal someone else’s solution. Don’t feel sorry, it’s just a numbers game.
Time complexity: \(O(kn^2)\)
Space complexity: \(O(kn)\)
fun getLengthOfOptimalCompression(s: String, k: Int): Int {
val dp = mutableMapOf<Pair<Int, Int>, Int>()
fun dfs(i: Int, toRemove: Int): Int =
if (toRemove < 0) Int.MAX_VALUE / 2
else if (i >= s.length - toRemove) 0
else dp.getOrPut(i to toRemove) {
val freq = IntArray(128)
var mostFreq = 0
(i..s.lastIndex).minOf { j ->
mostFreq = max(mostFreq, ++freq[s[j].toInt()])
when (mostFreq) {
0 -> 0
1 -> 1
else -> mostFreq.toString().length + 1
} + dfs(j + 1, toRemove - (j - i + 1 - mostFreq))
}
}
return dfs(0, k)
}
1578. Minimum Time to Make Rope Colorful medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/452
Min sum of removed duplicates in array.
The brute-force approach is to just consider keeping/remove every item, that can be cached in [size, 26] array.
However, there is a more optimal greedy solution: scan symbols one by one, and from each duplicate island remove the maximum of it.
Start from writing more verbose solution, keeping separate variables for currentSum
, totalSum
, and two separate conditions: if we meet a duplicate or not.
Then optimize it out.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun minCost(colors: String, neededTime: IntArray): Int {
var sum = 0
var max = 0
var prev = '.'
for ((i, c) in colors.withIndex()) {
sum += neededTime[i]
if (prev != c) sum -= max.also { max = 0 }
max = max(max, neededTime[i])
prev = c
}
return sum - max
}
1155. Number of Dice Rolls With Target Sum medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/451
Ways to throw once n
dices with k
faces to make target
sum.
Let’s consider each dice and try all the possible face. By repeating the process for all the dices, check if the final sum is equal to the target. The result will only depend on the dice count and target sum, so it can be cached.
Write brute force DFS, than add HashMap or array cache.
Time complexity: \(O(nkt)\), nt - is a DFS search space, k - is the iteration inside
Space complexity: \(O(nt)\)
fun numRollsToTarget(n: Int, k: Int, target: Int): Int {
val dp = mutableMapOf<Pair<Int, Int>, Int>()
fun dfs(c: Int, s: Int): Int =
dp.getOrPut(c to s) { when {
c == 0 -> if (s == 0) 1 else 0
s <= 0 -> 0
else -> (1..k).fold(0) { ways, d ->
(ways + dfs(c - 1, s - d)) % 1_000_000_007
}
} }
return dfs(n, target)
}
91. Decode Ways medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/450
Ways to decode back ‘A’ -> ‘1’, ‘B’ -> ‘2’ … ‘Z’ -> ‘26’
Let’s consider each position and do a DFS to check how many successfull paths exist.
For each position, we know the answer for the rest of the string, so it can be cached.
Start from implementing brute-force DFS, consider two cases: take just one char and take two chars. After that, introduce the cache, it can be an array or a HashMap<position, result>. Extra step, is to notice, the current value only depends on the two next values, so rewrite DFS into a reversed loop and store two previous results. The boss step is to do some code golf.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun numDecodings(s: String): Int =
s.indices.reversed().fold(0 to 1) { (prev, curr), i ->
curr to if (s[i] == '0') 0 else
curr + if (s.drop(i).take(2).toInt() in 10..26) prev else 0
}.second
1758. Minimum Changes To Make Alternating Binary String easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/449
Minimum operations to make 01
-string with no two adjacent equal
There are only two possible final variations - odd zeros even ones or even zeros odd ones. We can count how many positions to changes for each of them, then return smallest counter.
In a stressfull situation better to just use 4 counters: oddOnes, evenOnes, oddZeros, evenZeros. Then do something with them.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun minOperations(s: String): Int {
var oddOnesEvenZeros = 0
var oddZerosEvenOnes = 0
for (i in s.indices) when {
s[i] == '0' && i % 2 == 0 -> oddZerosEvenOnes++
s[i] == '0' && i % 2 != 0 -> oddOnesEvenZeros++
s[i] == '1' && i % 2 == 0 -> oddOnesEvenZeros++
s[i] == '1' && i % 2 != 0 -> oddZerosEvenOnes++
}
return min(oddOnesEvenZeros, oddZerosEvenOnes)
}
1496. Path Crossing easy blog post substack
https://t.me/leetcode_daily_unstoppable/448
Is path string of ‘N’, ‘E’, ‘W’, ‘S’ crosses
We can simulate the path and remember visited coordinates
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun isPathCrossing(path: String): Boolean {
val visited = mutableSetOf(0 to 0)
var x = 0
var y = 0
return !path.all { when (it) {
'N' -> y++
'S' -> y--
'E' -> x++
else -> x-- }
visited.add(x to y)
}
}
1422. Maximum Score After Splitting a String easy blog post substack
https://t.me/leetcode_daily_unstoppable/447
Max left_zeros + right_ones in 01-array
We can count ones
and then scan from the beginning modifying the ones
and zeros
counts. After some retrospect, we can do this with score
variable.
Time complexity: \(O(n)\)
Space complexity:
\(O(1)\), dropLast(1) creates the second list, but we can just use pointers or asSequence
fun maxScore(s: String): Int {
var score = s.count { it == '1' }
return s.dropLast(1).maxOf {
if (it == '0') ++score else --score
}
}
1637. Widest Vertical Area Between Two Points Containing No Points easy blog post substack
https://t.me/leetcode_daily_unstoppable/446
Max x window between xy points
We can sort points by x
and scan max window between them
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun maxWidthOfVerticalArea(points: Array<IntArray>): Int =
points
.sortedBy { it[0] }
.windowed(2)
.maxOf { it[1][0] - it[0][0] }
2706. Buy Two Chocolates easy blog post substack
https://t.me/leetcode_daily_unstoppable/445
Money change after two chocolates bought
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun buyChoco(prices: IntArray, money: Int): Int {
var (a, b) = Int.MAX_VALUE to Int.MAX_VALUE
for (x in prices)
if (x < a) a = x.also { b = a }
else if (x < b) b = x
return (money - a - b).takeIf { it >= 0 } ?: money
}
661. Image Smoother easy blog post substack
https://t.me/leetcode_daily_unstoppable/444
3x3 average of each cell in 2D matrix
Time complexity: \(O(nm)\)
Space complexity: \(O(nm)\)
fun imageSmoother(img: Array<IntArray>): Array<IntArray> =
Array(img.size) {
val ys = (max(0, it - 1)..min(img.lastIndex, it + 1)).asSequence()
IntArray(img[0].size) {
val xs = (max(0, it - 1)..min(img[0].lastIndex, it + 1)).asSequence()
ys.flatMap { y -> xs.map { img[y][it] } }.average().toInt()
}
}
1913. Maximum Product Difference Between Two Pairs easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/443
max * second_max - min * second_min
We can sort an array, or just find max and second max in a linear way.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun maxProductDifference(nums: IntArray): Int {
var (a, b, c, d) = listOf(0, 0, Int.MAX_VALUE, Int.MAX_VALUE)
for (x in nums) {
if (x > a) b = a.also { a = x } else if (x > b) b = x
if (x < d) c = d.also { d = x } else if (x < c) c = x
}
return a * b - c * d
}
2353. Design a Food Rating System medium blog post substack
https://t.me/leetcode_daily_unstoppable/442
Given foods, cuisines and ratings implement efficient methods changeRating(food, newRating) and highestRated(cuisine)
Given that we must maintain sorted order by rating
and be able to change the rating, the TreeSet
may help, as it provides O(logN) amortized time for remove(obj)
.
Start with inefficient implementation, like do the linear search in both methods. Then decide what data structures can help to quickly find an item.
constructor
should also be efficientTime complexity: \(O(log(n))\) for either method
Space complexity: \(O(n)\)
class FoodRatings(val foods: Array<String>, val cuisines: Array<String>, val ratings: IntArray) {
val foodToInd = foods.indices.groupBy { foods[it] }
val cuisineToInds: MutableMap<String, TreeSet<Int>> = mutableMapOf()
init {
for (ind in cuisines.indices)
cuisineToInds.getOrPut(cuisines[ind]) {
TreeSet(compareBy({ -ratings[it] }, { foods[it] }))
} += ind
}
fun changeRating(food: String, newRating: Int) {
val ind = foodToInd[food]!![0]
if (ratings[ind] != newRating) {
val sortedInds = cuisineToInds[cuisines[ind]]!!
sortedInds.remove(ind)
ratings[ind] = newRating
sortedInds.add(ind)
}
}
fun highestRated(cuisine: String): String = foods[cuisineToInds[cuisine]!!.first()]
}
242. Valid Anagram easy blog post substack
https://t.me/leetcode_daily_unstoppable/440
Time complexity: \(O(n)\)
Space complexity:
\(O(n)\), can also be solved in O(1) by computing the hash
fun isAnagram(s: String, t: String): Boolean =
s.groupBy { it } == t.groupBy { it }
1436. Destination City easy blog post substack
https://t.me/leetcode_daily_unstoppable/439
Time complexity: \(O(n)\)
Space complexity:
\(O(n)\), with toSet
fun destCity(paths: List<List<String>>): String =
(paths.map { it[1] } - paths.map { it[0] }).first()
2482. Difference Between Ones and Zeros in Row and Column easy blog post substack
https://t.me/leetcode_daily_unstoppable/438
diff[i][j] = onesRowi + onesColj - zerosRowi - zerosColj
Time complexity: \(O(nm)\)
Space complexity: \(O(nm)\)
fun onesMinusZeros(grid: Array<IntArray>): Array<IntArray> {
val onesRow = grid.map { it.count { it == 1 } }
val zerosRow = grid.map { it.count { it == 0 } }
val onesCol = grid[0].indices.map { x -> grid.indices.count { grid[it][x] == 1 } }
val zerosCol = grid[0].indices.map { x -> grid.indices.count { grid[it][x] == 0 } }
return Array(grid.size) { y -> IntArray(grid[0].size) { x ->
onesRow[y] + onesCol[x] - zerosRow[y] - zerosCol[x]
}}
}
1582. Special Positions in a Binary Matrix easy blog post substack
https://t.me/leetcode_daily_unstoppable/437
Time complexity: \(O((nm)^2)\)
Space complexity: \(O(1)\)
fun numSpecial(mat: Array<IntArray>): Int {
var count = 0
for (y in 0..mat.lastIndex)
for (x in 0..mat[y].lastIndex)
if (mat[y][x] == 1
&& (0..mat.lastIndex).filter { it != y }.all { mat[it][x] == 0}
&& (0..mat[y].lastIndex).filter { it != x }.all { mat[y][it] == 0})
count++
return count
}
1464. Maximum Product of Two Elements in an Array easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/436
We can sort, we can search twice for indices, we can scan once with two variables.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun maxProduct(nums: IntArray): Int = with(nums.indices){
maxBy { nums[it] }.let { i ->
(nums[i] - 1) * (nums[filter { it != i }.maxBy { nums[it] }] - 1)
}}
1287. Element Appearing More Than 25% In Sorted Array easy blog post substack
https://t.me/leetcode_daily_unstoppable/435
Most frequent element
Time complexity: \(O(n)\)
Space complexity: \(O(n)\), can be O(1)
fun findSpecialInteger(arr: IntArray): Int =
arr.groupBy { it }
.maxBy { (k, v) -> v.size }!!
.key
867. Transpose Matrix easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/434
Transpose 2D matrix
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun transpose(matrix: Array<IntArray>): Array<IntArray> =
Array(matrix[0].size) { x ->
IntArray(matrix.size) { y ->
matrix[y][x]
}
}
94. Binary Tree Inorder Traversal easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/433
Inorder traversal
Nothing special. For the iterative solution we can use Morris traversal.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun inorderTraversal(root: TreeNode?): List<Int> = root?.run {
inorderTraversal(left) + listOf(`val`) + inorderTraversal(right)
} ?: listOf<Int>()
606. Construct String from Binary Tree easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/432
Pre-order binary tree serialization
Let’s write a recursive solution.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun tree2str(root: TreeNode?): String = root?.run {
val left = tree2str(left)
val right = tree2str(right)
val curr = "${`val`}"
if (left == "" && right == "") curr
else if (right == "") "$curr($left)"
else "$curr($left)($right)"
} ?: ""
1903. Largest Odd Number in String easy blog post substack
https://t.me/leetcode_daily_unstoppable/431
Largest odd number in a string
Just search for the last odd
Let’s write Kotlin one-liner
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun largestOddNumber(num: String): String =
num.dropLastWhile { it.toInt() % 2 == 0 }
1688. Count of Matches in Tournament easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/428
Count of odd-even matches according to the rules x/2
or 1+(x-1)/2
.
The naive solution is to just implement what is asked.
Then you go read others people solutions and found this: n-1
.
Time complexity: \(O(log(n))\)
Space complexity: \(O(1)\)
fun numberOfMatches(n: Int): Int {
var x = n
var matches = 0
while (x > 1) {
if (x % 2 == 0) {
matches += x / 2
x = x / 2
} else {
matches += (x - 1) / 2
x = 1 + (x - 1) / 2
}
}
return matches
}
2264. Largest 3-Same-Digit Number in String easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/427
Largest 3-same-digit number in a string
There are totally 10 such numbers: 000, 111, ..., 999
.
Let’s use Kotlin’s API
Time complexity: \(O(n)\)
Space complexity:
\(O(n)\), can be O(1) with asSequence()
fun largestGoodInteger(num: String): String =
num.windowed(3)
.filter { it[0] == it[1] && it[0] == it[2] }
.maxByOrNull { it[0] } ?: ""
1266. Minimum Time Visiting All Points easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/426
Path coordinates distance in XY plane
For each pair of points lets compute diagonal distance and the remainder: time = diag + remainder
. Given that remainder = max(dx, dy) - diag
, we derive the formula.
Let’s use some Kotlin’s API:
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun minTimeToVisitAllPoints(points: Array<IntArray>): Int =
points.asSequence().windowed(2).sumBy { (from, to) ->
max(abs(to[0] - from[0]), abs(to[1] - from[1]))
}
1160. Find Words That Can Be Formed by Characters easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/425
Sum of words
lengths constructed by chairs
Just use the char frequencies map
Some Kotlin’s API:
Time complexity: \(O(n)\)
Space complexity: \(O(n)\), can be O(1)
fun countCharacters(words: Array<String>, chars: String): Int =
chars.groupBy { it }.let { freq ->
words.sumBy {
val wfreq = it.groupBy { it }
if (wfreq.keys.all { freq[it] != null
&& wfreq[it]!!.size <= freq[it]!!.size })
it.length else 0
}
}
1662. Check If Two String Arrays are Equivalent easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/423
Two dimensional array equals
There is a one-liner that takes O(n) memory: ord1.joinToString("") == word2.joinToString("")
. Let’s use two-pointer approach to reduce the memory footprint.
for
on a first word, and use the pointer variable for the secondTime complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun arrayStringsAreEqual(word1: Array<String>, word2: Array<String>): Boolean {
var i = 0
var ii = 0
for (w in word1) for (c in w) {
if (i >= word2.size) return false
if (c != word2[i][ii]) return false
ii++
if (ii >= word2[i].length) {
i++
ii = 0
}
}
return i == word2.size
}
1611. Minimum One Bit Operations to Make Integers Zero hard blog post substack youtube
https://t.me/leetcode_daily_unstoppable/421
Minimum rounds of inverting rightmost bit or left of the rightmost 1
bit to make n
zero
Let’s observe the example:
// 6
// 110
// 010 b
// 011 a
// 001 b
// 000 a
// 10 = 2 + f(1) = 2^1 + f(2^0)
// 11 a
// 01 b -> f(1)
// 100 = 4 + f(10) = 2^2 + f(2^1)
// 101 a
// 111 b
// 110 a
// 010 b -> f(10)
// 1000 = 8 + f(100) = 2^3 + f(2^2)
// 1001 a
// 1011 b
// 1010 a
// 1110 b
// 1111 a
// 1101 b
// 1100 a
// 0100 b -> f(100)
There are two tricks we can derive:
1101
, we do f(0b1101) = f(0b1000) - f(0b100) + f(0b1)
.Time complexity: \(O(log(n))\)
Space complexity: \(O(log(n))\)
fun minimumOneBitOperations(n: Int): Int {
val f = HashMap<Int, Int>()
f[0] = 0
f[1] = 1
var curr = 2
while (curr > 0) {
f[curr] = curr + f[curr / 2]!!
curr *= 2
}
var res = 0
var sign = 1;
for (i in 0..31) {
val bit = 1 shl i
if (n and bit != 0) {
res += sign * f[bit]!!
sign = -sign
}
}
return Math.abs(res)
}
191. Number of 1 Bits easy blog post substack youtube
https://t.me/leetcode_daily_unstoppable/420
Bits count
The optimal solution would be using built-in n.countOneBits()
.
However, there is a knonw technique using tabulation DP to count bits. The recurrence is: count(n) = count(n « 1) + 1?1:0. For example, count(1111) = 1 + count(111). Or, count(110) = 0 + count(11)
Time complexity: \(O(1)\)
Space complexity: \(O(1)\)
val dp = IntArray(256).apply {
for (i in 1..<size)
this[i] = this[i / 2] + (i and 1)
}
fun hammingWeight(n:Int):Int =
dp[n and 255] +
dp[(n ushr 8) and 255] +
dp[(n ushr 16) and 255] +
dp[(n ushr 24) and 255]
2147. Number of Ways to Divide a Long Corridor hard blog post substack youtube
https://t.me/leetcode_daily_unstoppable/419
Count ways to place borders separating pairs of ‘S’ in ‘SP’ string
We can scan linearly and do the interesting stuff after each two ‘S’: each new ‘P’ adds ‘sum’ ways to the total. The last pair of ‘S’ don’t need a border.
// ssppspsppsspp
// ss 1
// ssp 2
// sspp 3
// sps 3
// spsp 3+3=6
// spspp 6+3=9 <-- return this
// ss 9
// ssp 9+9=18
// sspp 18+9=27 discard this result, as it is last
Carefult what ‘sum’ to add, save the last sum to a separate variable.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun numberOfWays(corridor: String): Int {
var prev = 1
var sum = 1
var s = 0
for (c in corridor)
if (c == 'S') {
if (s == 2) {
prev = sum
s = 0
}
s++
} else if (s == 2)
sum = (prev + sum) % 1_000_000_007
return if (s == 2) prev else 0
}
935. Knight Dialer medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/418
Count of dialer n
-length numbers formed by pressing in a chess Knight’s moves
We can search with Depth-First Search for every position and count every path that has n
digits in it.
The result will only depend on a previous number and count of the remaining moves, so can be cached.
Let’s write a separate paths
map: current digit to next possible.
Time complexity:
\(O(n)\), 10
digits is a constant value
Space complexity: \(O(n)\)
val dp = mutableMapOf<Pair<Int, Int>, Int>()
val paths = mapOf(
-1 to (0..9).toList(),
0 to listOf(4, 6),
1 to listOf(6, 8),
2 to listOf(7, 9),
3 to listOf(4, 8),
4 to listOf(3, 9, 0),
5 to listOf(),
6 to listOf(1, 7, 0),
7 to listOf(2, 6),
8 to listOf(1, 3),
9 to listOf(2, 4))
fun knightDialer(pos: Int, prev: Int = -1): Int =
if (pos == 0) 1 else dp.getOrPut(pos to prev) {
paths[prev]!!.map { knightDialer(pos - 1, it) }
.fold(0) { r, t -> (r + t) % 1_000_000_007 }
}
1727. Largest Submatrix With Rearrangements medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/417
Max area of 1
submatrix after sorting columns optimally
Use hint :(
Ok, if we store the heights of the columns we can analyze each row independently, by choosing the largest heights first. The area will be height * width
, where width will be the current position:
We can reuse the matrix, but don’t do this in a production code without a warning.
Time complexity: \(O(nmlog(m))\)
Space complexity: \(O(1)\)
fun largestSubmatrix(matrix: Array<IntArray>): Int {
for (y in 1..<matrix.size)
for (x in 0..<matrix[y].size)
if (matrix[y][x] > 0)
matrix[y][x] += matrix[y - 1][x]
var max = 0
for (row in matrix) {
row.sort()
for (x in row.lastIndex downTo 0)
max = max(max, row[x] * (row.size - x))
}
return max
}
1685. Sum of Absolute Differences in a Sorted Array medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/416
Array to sum_j(abs(arr[i] - arr[j]))
for each i
This is an arithmetic problem. We need to pay attention of an abs
sign, given the array in a sorted order.
// 0 1 2 3 4 5
// a b c d e f
// c: c-a + c-b + c-c + d-c + e-c + f-c
// c * (1 + 1 + 1-1 -1 -1-1) -a-b+d+e+f
// (i+1 - (size + 1 - (i + 1)))
// (i + 1 - size - 1 +i + 1)
// (2*i - size + 1)
// d: d-a + d-b + d-c + d-d + e-d +f-d
// d * (1+1+1+1-1-1-1)
// i=3 2*3-6+1=1
// soFar = a+b
// sum = a+b+c+d+e+f
// i = 2
// curr = sum - soFar + nums[i] * (2*i - size + 1)
// 2 3 5
// sum = 10
// soFar = 2
// i=0 10 - 2 + 2 * (2*0-3+1)=10-6=4 xxx
// 2-2 + 3-2 + 5-2 = 2 * (1-1-1-1) + (3 + 5)
// 3-2 + 3-3 + 5-3 = 3 * (1+1-1-1) - 2 + (5)
// (2*1-3+1) (sum-soFar)
// 5-2 + 5-3 + 5-5 = 5 * (1+1+1-1) -2-3 + (0)
Evaluate some examples, then derive the formula.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun getSumAbsoluteDifferences(nums: IntArray): IntArray {
val sum = nums.sum()
var soFar = 0
return IntArray(nums.size) { i ->
soFar += nums[i]
(sum - 2 * soFar + nums[i] * (2 * i - nums.size + 2))
}
}
1561. Maximum Number of Coins You Can Get medium blog post substack youtube
https://t.me/leetcode_daily_unstoppable/415
Get sum of second maxes of triples from array
Observing the example:
// 1 2 3 4 5 6 7 8 9
// * * * 8
// * * * 6
// * * * 4
// size = x + 2x
we can deduce an optimal algorithm: give bob the smallest value, and take the second largest. There are exactly size / 3
moves total.
Let’s write it in a functional style, using Kotlin’s API:
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\), can be O(1) when sorted in-place
fun maxCoins(piles: IntArray): Int =
piles.sorted()
.drop(piles.size / 3)
.chunked(2)
.sumBy { it[0] }
1630. Arithmetic Subarrays medium blog post substack
https://t.me/leetcode_daily_unstoppable/414
Query array ranges can form arithmetic sequence
Given the problem contraints, the naive solution would work: just sort the subarray and check the diff
.
We can use PriorityQueue
Time complexity: \(O(n^2log(n))\)
Space complexity: \(O(n)\)
fun checkArithmeticSubarrays(nums: IntArray, l: IntArray, r: IntArray) =
List(l.size) { ind ->
val pq = PriorityQueue<Int>()
for (i in l[ind]..r[ind]) pq.add(nums[i])
val diff = -pq.poll() + pq.peek()
var prev = pq.poll()
while (pq.isNotEmpty()) {
if (pq.peek() - prev != diff) return@List false
prev = pq.poll()
}
true
}
1424. Diagonal Traverse II medium blog post substack
https://t.me/leetcode_daily_unstoppable/413
Diagonal 2D matrix order with prunes
The naive solution is to adjust the pointers x
and y
. However, that will cost O(max(x)*max(y)) and give TLE.
Let’s just sort indices pairs (x y)
and take them one by one.
Use some Kotlin’s features:
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun findDiagonalOrder(nums: List<List<Int>>): IntArray =
with(PriorityQueue<Pair<Int, Int>>(compareBy(
{ it.first + it.second }, { it.first }, { it.second }
))) {
for (y in nums.indices)
for (x in nums[y].indices) add(x to y)
IntArray(size) { poll().let { (x, y) -> nums[y][x]} }
}
1814. Count Nice Pairs in an Array medium blog post substack
https://t.me/leetcode_daily_unstoppable/412
Count pairs x-rev(x) == y-rev(y)
, where rev(123) = 321
For simplicity, let’s redefine the equation, keeping i
and j
on a separate parts \(nums[i] - rev(nums[i]) == nums[j] - rev(nums[j])\). Now, we can precompute nums[i] - rev(nums[i])
. The remaining part of an algorithm is how to calculate count of the duplicate numbers in a linear scan.
Let’s use a HashMap to count the previous numbers count. Each new number will make a count
new pairs.
Time complexity:
\(O(nlg(n))\), lg(n) - for the rev()
Space complexity: \(O(n)\)
fun countNicePairs(nums: IntArray): Int {
val counts = HashMap<Int, Int>()
var sum = 0
for (x in nums) {
var n = x
var rev = 0
while (n > 0) {
rev = (n % 10) + rev * 10
n = n / 10
}
val count = counts[x - rev] ?: 0
sum = (sum + count) % 1_000_000_007
counts[x - rev] = count + 1
}
return sum
}
2391. Minimum Amount of Time to Collect Garbage medium blog post substack
https://t.me/leetcode_daily_unstoppable/411
Time to pick 3-typed garbage[]
by 3 trucks traveling to the right travel[]
time
We can hardcode the algorithm from the description examples, for each truck individually.
Let’s try to minify the code:
garbage.sumBy { it.length }
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun garbageCollection(garbage: Array<String>, travel: IntArray): Int =
garbage.sumBy { it.length } +
"MPG".sumBy { c ->
(1..garbage.indexOfLast { c in it }).sumBy { travel[it - 1] }
}
1887. Reduction Operations to Make the Array Elements Equal medium blog post substack
https://t.me/leetcode_daily_unstoppable/410
Number of operations to decrease all elements to the next smallest
The algorithm pretty much in a problem definition, just implement it.
Time complexity: \(O(nlog())\)
Space complexity: \(O(n)\)
fun reductionOperations(nums: IntArray): Int {
nums.sort()
return (nums.size - 2 downTo 0).sumBy {
if (nums[it] < nums[it + 1]) nums.size - 1 - it else 0
}
}
1838. Frequency of the Most Frequent Element medium blog post substack
https://t.me/leetcode_daily_unstoppable/408
Max count of equal numbers if increment arr[i]
k
times
Let’s sort the array and scan numbers from small to hi. As we’re doing only the increment operations, only the left part of the current position matters. Let’s see how much items we can make equal to the current arr[i]
:
// 1 4 8 13 inc
// 4 4 8 13 3
// ^
// 8 8 ^ 3 + 2 * (8 - 4) = 8 + 3 = 12
// 1 8 ^ 12 - (8 - 1) = 4
When taking a new element 8
, our total increment operations inc
grows by the difference between two previous 4 4
and the current 8
.
If inc
becomes bigger than k
, we can move the from
position, returning nums[i] - nums[from]
operations back.
from
and to
max
0
element position, and iterate from 1
to avoid overthinkingTime complexity: \(O(nlog(n))\)
Space complexity: \(O(1)\)
fun maxFrequency(nums: IntArray, k: Int): Int {
nums.sort()
var from = 0
var inc = 0
var max = 1
for (to in 1..<nums.size) {
inc += (to - from) * (nums[to] - nums[to - 1])
while (from <= to && inc > k)
inc -= nums[to] - nums[from++]
max = max(max, to - from + 1)
}
return max
}
1877. Minimize Maximum Pair Sum in Array medium blog post substack
https://t.me/leetcode_daily_unstoppable/407
Minimum possible max of array pairs sums
The optimal construction way is to pair smallest to largest.
We can use two pointers and iteration, let’s write non-optimal one-liner however
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(1)\), this solution takes O(n), but can be rewritten
fun minPairSum(nums: IntArray): Int =
nums.sorted().run {
zip(asReversed()).maxOf { it.first + it.second }
}
1980. Find Unique Binary String medium blog post substack
https://t.me/leetcode_daily_unstoppable/406
First absent number in a binary string array
The naive solution would be searching in all the numbers 0..2^n
. However, if we convert strings to ints and sort them, we can do a linear scan to detect first absent.
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun findDifferentBinaryString(nums: Array<String>): String {
var next = 0
for (x in nums.sorted()) {
if (x.toInt(2) > next) break
next++
}
return next.toString(2).padStart(nums[0].length, '0')
}
1846. Maximum Element After Decreasing and Rearranging medium blog post substack
https://t.me/leetcode_daily_unstoppable/405
Max number from converting array to non decreasing
First, sort the array. Now, for every missing number, 1 3 5
-> 2
we can take one of the numbers from the highest, 1 2 3
.
We can use a counter and a Priority Queue.
For example:
array: 1 5 100 100 100
counter: 1 2 3 4 5
Let’s use some Kotlin’s sugar:
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun maximumElementAfterDecrementingAndRearranging(arr: IntArray): Int =
with(PriorityQueue<Int>().apply { addAll(arr.asList()) }) {
var max = 0
while (isNotEmpty()) if (poll() > max) max++
max
}
Shorter version:
1930. Unique Length-3 Palindromic Subsequences medium blog post substack
https://t.me/leetcode_daily_unstoppable/403
Count of unique palindrome substrings of length 3
We can count how many other characters between group of the current
Let’s use Kotlin API:
Time complexity:
\(O(n)\), we can also use withIndex
to avoid searching indexOf
and lastIndexOf
.
Space complexity:
\(O(1)\), if we store frequencies in an IntArray
fun countPalindromicSubsequence(s: String): Int {
val freq = s.groupBy { it }.filterValues { it.size > 1 }
var count = 0
for ((l, f) in freq) {
if (f.size > 2) count++
val visited = HashSet<Char>()
for (i in s.indexOf(l)..s.lastIndexOf(l))
if (s[i] != l && visited.add(s[i])) count++
}
return count
}
2785. Sort Vowels in a String medium blog post substack
https://t.me/leetcode_daily_unstoppable/402
Sort vowels in a string
The sorted result will only depend of the vowels frequencies.
Let’s use Kotlin API:
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun sortVowels(s: String): String {
val freq = s.groupBy { it }.mapValues({ it.value.size }).toMutableMap()
val vl = mutableListOf('A', 'E', 'I', 'O', 'U', 'a', 'e', 'i', 'o', 'u')
val vs = vl.toSet()
return buildString {
for (c in s)
if (c in vs) {
while (freq[vl.first()].let { it == null || it <= 0 }) vl.removeFirst()
freq[vl.first()] = freq[vl.first()]!! - 1
append(vl.first())
} else append(c)
}
}
815. Bus Routes hard blog post substack
https://t.me/leetcode_daily_unstoppable/401
Minimum buses to travel by given routes
The Breadth-First Search in a routes graph would work.
Build stop to route
association to know which of the routes are next.
Some optimizations:
source == target
stopToRoute
graphroutes.size
buses neededTime complexity: \(O(RS)\)
Space complexity: \(O(RS)\)
fun numBusesToDestination(routes: Array<IntArray>, source: Int, target: Int): Int {
if (source == target) return 0
val stopToRoute = mutableMapOf<Int, MutableList<Int>>()
for (i in routes.indices)
for (stop in routes[i])
stopToRoute.getOrPut(stop) { mutableListOf() } += i
return with(ArrayDeque<Int>()) {
add(source)
val visited = mutableSetOf<Int>()
for (bus in 1..routes.size)
repeat(size) {
for (route in stopToRoute.remove(removeFirst()) ?: emptyList())
if (visited.add(route)) for (s in routes[route])
if (s == target) return@with bus else add(s)
}
-1
}
}
2642. Design Graph With Shortest Path Calculator hard blog post substack
Implement graph with shortest path searching
There is no special knowledge here, just a simple Dijkstra, that is BFS in a space of the shortest-so-far paths
visited
set will improve the speedTime complexity: \(O(Vlog(E))\)
Space complexity: \(O(E)\)
class Graph(n: Int, edges: Array<IntArray>) :
HashMap<Int, MutableList<IntArray>>() {
init { for (e in edges) addEdge(e) }
fun addEdge(edge: IntArray) {
getOrPut(edge[0]) { mutableListOf() } += edge
}
fun shortestPath(node1: Int, node2: Int): Int =
with(PriorityQueue<Pair<Int, Int>>(compareBy({ it.second }))) {
add(node1 to 0)
val visited = HashSet<Int>()
while (isNotEmpty()) {
val (n, wp) = poll()
if (n == node2) return@with wp
if (visited.add(n))
get(n)?.onEach { (_, s, w) -> add(s to (w + wp))}
}
-1
}
}
1743. Restore the Array From Adjacent Pairs medium blog post substack
https://t.me/leetcode_daily_unstoppable/399
Restore an array from adjacent pairs
We can form an undirected graph and do a Depth-First Search
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun restoreArray(adjacentPairs: Array<IntArray>): IntArray {
val fromTo = mutableMapOf<Int, MutableList<Int>>()
for ((from, to) in adjacentPairs) {
fromTo.getOrPut(from) { mutableListOf() } += to
fromTo.getOrPut(to) { mutableListOf() } += from
}
val visited = HashSet<Int>()
with(ArrayDeque<Int>()) {
add(fromTo.keys.first { fromTo[it]!!.size == 1 }!!)
return IntArray(adjacentPairs.size + 1) {
while (first() in visited) removeFirst()
removeFirst().also {
visited.add(it)
fromTo[it]?.onEach { add(it) }
}
}
}
}
1759. Count Number of Homogenous Substrings medium blog post substack
https://t.me/leetcode_daily_unstoppable/398
Count of substrings of same chars
Just count current len and add to total
// abbcccaa c t
// a 1 1
// b 1 2
// b 2 4
// c 1 5
// c 2 7
// c 3 10
// a 1 11
// a 2 13
prev
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun countHomogenous(s: String): Int {
var total = 0
var count = 0
var prev = '.'
for (c in s) {
if (c == prev) count++
else count = 1
total = (total + count) % 1_000_000_007
prev = c
}
return total
}
2849. Determine if a Cell Is Reachable at a Given Time medium blog post substack
https://t.me/leetcode_daily_unstoppable/397
Is path possible on grid sx, sy -> fx, fy
Given the problem size, we can’t use DP, as it will take O(n^2). However, we must notice, that if the shortest path is reachable, than any other path can be formed to travel at any time.
The shortest path will consist of only the difference between coordinates.
Time complexity: \(O(1)\)
Space complexity: \(O(1)\)
fun isReachableAtTime(sx: Int, sy: Int, fx: Int, fy: Int, t: Int): Boolean {
var dx = Math.abs(fx - sx)
var dy = Math.abs(fy - sy)
var both = min(dx, dy)
var other = max(dx, dy) - both
var total = both + other
return total <= t && (total > 0 || t != 1)
}
1921. Eliminate Maximum Number of Monsters medium blog post substack
https://t.me/leetcode_daily_unstoppable/396
Count possible 1-minute
kills in a game of dist[]
targets falling with speed[]
Each target has it’s own arrival time_i = dist[i] / speed[i]
. We must prioritize targets by it.
Let’s use Kotlin API:
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun eliminateMaximum(dist: IntArray, speed: IntArray): Int =
dist.indices.sortedBy { dist[it].toDouble() / speed[it] }
.withIndex()
.takeWhile { (time, ind) -> speed[ind] * time < dist[ind] }
.count()
1845. Seat Reservation Manager medium blog post substack
https://t.me/leetcode_daily_unstoppable/395
Design reservation number system
The naive approach is to just use PriorityQueue as is:
class SeatManager(n: Int): PriorityQueue<Int>() {
init { for (x in 1..n) add(x) }
fun reserve() = poll()
fun unreserve(seatNumber: Int) = add(seatNumber)
}
However, we can improve the memory cost by noticing, that we can shrink the heap when max
is returned.
Time complexity: \(O(log(n))\) for operations
Space complexity: \(O(n)\)
class SeatManager(n: Int): PriorityQueue<Int>() {
var max = 0
fun reserve() = if (isEmpty()) ++max else poll()
fun unreserve(seatNumber: Int) {
if (seatNumber == max) max--
else add(seatNumber)
}
}
1535. Find the Winner of an Array Game medium blog post substack
https://t.me/leetcode_daily_unstoppable/394
Find maximum of the k
nearest in array
Looking at some examples:
// 0 1 2 3 4 5
// 1 3 2 5 4 10 3
// 3 2 5 4 10 1 3
// 3 5 4 10 1 2 5
// 5 4 10 1 2 3 5
// 5 10 1 2 3 4 10
// 10 1 2 3 4 5 10 ...
we can deduce that the problem is trivial when k >= arr.size
- it is just a maximum.
Now, when k < arr.size
we can just simulate the given algorithm and stop on the first k
-winner.
1..arr.lastIndex
or use a clever initialization wins = -1
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun getWinner(arr: IntArray, k: Int): Int {
var wins = -1
var max = arr[0]
for (x in arr) {
if (x > max) {
wins = 1
max = x
} else wins++
if (wins == k) break
}
return max
}
1503. Last Moment Before All Ants Fall Out of a Plank medium blog post substack
https://t.me/leetcode_daily_unstoppable/392
Max time ants on a line when goint left and right
Use the hint: ants can pass through
The problem becomes trivial
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun getLastMoment(n: Int, left: IntArray, right: IntArray): Int =
max(left.maxOrNull() ?: 0, n - (right.minOrNull() ?: n))
767. Reorganize String medium blog post substack
https://t.me/leetcode_daily_unstoppable/391
Non-repeating consequent chars string from another string
The naive brute force solution is to do Depth-First Search and memoization by given current char, previous one and used chars set. It gives TLE, as it takes O(n^3).
Next, use hint
.
To take chars one by one from the two most frequent we will use a PriorityQueue
buildString
, compareByDescending
, onEach
Time complexity:
\(O(n)\), assume constant 128log(128)
for a Heap sorting
Space complexity: \(O(n)\)
fun reorganizeString(s: String): String = buildString {
val freq = IntArray(128)
s.onEach { freq[it.toInt()]++ }
val pq = PriorityQueue<Char>(compareByDescending { freq[it.toInt()] })
for (c in 'a'..'z') if (freq[c.toInt()] > 0) pq.add(c)
while (pq.isNotEmpty()) {
var c = pq.poll()
if (isNotEmpty() && last() == c) {
if (pq.isEmpty()) return ""
c = pq.poll()
pq.add(last())
}
append(c)
if (--freq[c.toInt()] > 0) pq.add(c)
}
}
2265. Count Nodes Equal to Average of Subtree medium blog post substack
https://t.me/leetcode_daily_unstoppable/390
Number of nodes in a tree where val == sum / count
of a subtree
Just do a Depth First Search and return sum
and count
of a subtree.
Time complexity: \(O(n)\)
Space complexity: \(O(log(n))\) for the recursion depth
fun averageOfSubtree(root: TreeNode?): Int {
var res = 0
fun dfs(n: TreeNode): Pair<Int, Int> {
val (ls, lc) = n.left?.let { dfs(it) } ?: 0 to 0
val (rs, rc) = n.right?.let { dfs(it) } ?: 0 to 0
val sum = n.`val` + ls + rs
val count = 1 + lc + rc
if (n.`val` == sum / count) res++
return sum to count
}
root?.let { dfs(it) }
return res
}
501. Find Mode in Binary Search Tree easy blog post substack
https://t.me/leetcode_daily_unstoppable/389
Most frequent elements in a Binary Search Tree
A simple solution is to use a frequency
map.
Another way is the linear scan of the increasing sequence. For example, 1 1 1 2 2 2 3 3 4 4 4
: we can use one counter and drop the previous result if counter is more than the previous max.
To convert the Binary Search Tree into an increasing sequence, we can do an in-order traversal.
Time complexity: \(O(n)\)
Space complexity:
\(O(n)\), result can be n
if numbers are unique
fun findMode(root: TreeNode?): IntArray {
val res = mutableListOf<Int>()
var maxCount = 0
var count = 0
var prev = Int.MAX_VALUE
fun dfs(n: TreeNode) {
n.left?.let { dfs(it) }
if (prev == n.`val`) {
count++
} else {
count = 1
prev = n.`val`
}
if (count == maxCount) {
res += n.`val`
} else if (count > maxCount) {
maxCount = count
res.clear()
res += n.`val`
}
n.right?.let { dfs(it) }
}
root?.let { dfs(it) }
return res.toIntArray()
}
2433. Find The Original Array of Prefix Xor medium blog post substack
https://t.me/leetcode_daily_unstoppable/387
Reverse xor
operation
Let’s observe how xor
works:
// 010 2
// 101 5
// 111 7
// 5 xor 7 = 2
// 101 xor 111 = 010
// 5 xor 2 = 101 xor 010 = 111
We can reverse the xor
operation by applying it again: a ^ b = c
, then a ^ c = b
There are several ways to write this:
mapIndexed
Let’s use Kotlin’s array constructor lambda and getOrElse
.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun findArray(pref: IntArray) = IntArray(pref.size) {
pref[it] xor pref.getOrElse(it - 1) { 0 }
}
1356. Sort Integers by The Number of 1 Bits easy blog post substack
https://t.me/leetcode_daily_unstoppable/386
Sort an array comparing by bit count and value
Let’s use some Kotlin API
countOneBits
sortedWith
compareBy
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun sortByBits(arr: IntArray): IntArray = arr
.sortedWith(compareBy({ it.countOneBits() }, { it }))
.toIntArray()
458. Poor Pigs hard blog post substack
https://t.me/leetcode_daily_unstoppable/385
Minimum pigs
to find a poison in buckets
in k
rounds
The first idea is, with the number of pigs increasing, the possibility to successfully test in the given time grows from impossible
to possible
. This gives us the idea to use a Binary Search
.
However, now we must solve another problem: given the pigs
and rounds
, how many buckets we can test?
One simple insight is: let’s assign unique pigs pattern
to each of the bucket.
We can brute-force this problem and use memorization. Consider each pig, it can avoid participation, and can participate in all the rounds:
val dp = mutableMapOf<Int, Int>()
fun numPatterns(pigs: Int): Int {
fun dfs(curr: Int): Int = if (curr == 0) 1 else dp.getOrPut(curr) {
val take = dfs(curr - 1)
if (take >= buckets) take else take + take * minutesToTest / minutesToDie
}
return dfs(pigs)
}
This number grows quickly, so we trim it by the buckets number maximum.
Another way to solve this, is to observe those unique patterns.
If we have just one round, 3 pigs, there are total 8 patterns:
// pigs = 3 rounds = 1
// 123
// 0 000 no pig drinks
// 1 001 pig #3 drinks
// 2 010 pig #2 drinks
// 3 011 pigs #2 and #3 drinks
// 4 100 pig #1 drinks
// 5 101 pigs #1 and #3 drinks
// 6 110 pigs #1 and #2 drinks
// 7 111 all pig drinks
or,
//
// 0 1 2 3 4 5 6 7
// 1 1 1 1 <-- pig #1
// 2 2 2 2 <-- pig #2
// 3 3 3 3 <-- pig #3
Now, if one bucket is a poison, we immediately know which one of those 8
buckets by its unique pattern.
Ok, so 3
pigs for 1
round enables to test 8
or 2^3
buckets. It is evident, that for 1
round the number of possible buckets is 2^pigs
How this changes with the growth of rounds? Let’s observe another example, 3
pigs, 2
rounds:
//
// 3 pigs, 2 rounds:
// 123
// 0 000
// 1 001
// 2 002
// 3 010
// 4 011
// 5 012
// 6 020
// 7 021
// 8 022
// 9 100
//10 101
//11 102
//12 110
//13 111
//14 112
//15 120
//16 121
//17 122
//18 200
//19 201
//20 202
//21 210
//22 211
//23 212
//24 220
//25 221
//26 222
or,
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// - round 1 -
// 1 1 1 1 1 1 1 1 1
// 2 2 2 2 2 2 2 2 2
// 3 3 3 3 3 3 3 3 3
// - round 2 -
// 1 1 1 1 1 1 1 1 1
// 2 2 2 2 2 2 2 2 2
// 3 3 3 3 3 3 3 3 3
Each pigs pattern
consists of the 3
pigs, and each pig defined as round 1 or round 2.
This results in 27
unique patterns, or buckets being able to test, or 3^3
. Let’s extrapolate this formula: buckets = (1 + rounds) ^ pigs
For better Binary Search, use:
lo
and hi
lo == hi
lo
or hi
min = min(min, mid)
Time complexity:
\(O(log^2(buckets))\), one log
for the Binary Search, another is for canTest
function
Space complexity: \(O(1)\)
DFS + memo
fun poorPigs(buckets: Int, minutesToDie: Int, minutesToTest: Int): Int {
val dp = mutableMapOf<Int, Int>()
fun numPatterns(pigs: Int): Int {
fun dfs(curr: Int): Int = if (curr == 0) 1 else dp.getOrPut(curr) {
val take = dfs(curr - 1)
if (take >= buckets) take else take + take * minutesToTest / minutesToDie
}
return dfs(pigs)
}
var lo = 0
var hi = buckets
var min = hi
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (numPatterns(mid) >= buckets) {
min = min(min, mid)
hi = mid - 1
} else lo = mid + 1
}
return min
}
The more clever version:
fun poorPigs(buckets: Int, minutesToDie: Int, minutesToTest: Int): Int {
fun canTest(pigs: Int): Boolean {
var p = 0
var bs = 1
while (p++ < pigs) {
bs *= 1 + minutesToTest / minutesToDie
if (bs >= buckets) return true
}
return bs >= buckets
}
var lo = 0
var hi = buckets
var min = hi
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (canTest(mid)) {
min = min(min, mid)
hi = mid - 1
} else lo = mid + 1
}
return min
}
1220. Count Vowels Permutation hard blog post substack
https://t.me/leetcode_daily_unstoppable/384
Count of n
lengths paths according to graph rules a
->e
, e
->(a
, i
), etc
This is a straghtforward DFS + memoization dynamic programming problem. Given the current position and the previous character, we know the suffix answer. It is independent of any other factors, so can be cached.
Let’s write DFS + memo
sumOf
APITime complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun countVowelPermutation(n: Int): Int {
val vs = mapOf('a' to arrayOf('e'),
'e' to arrayOf('a', 'i'),
'i' to arrayOf('a', 'e', 'o', 'u'),
'o' to arrayOf('i', 'u'),
'u' to arrayOf('a'),
'.' to arrayOf('a', 'e', 'i', 'o', 'u'))
val dp = mutableMapOf<Pair<Int, Char>, Long>()
fun dfs(i: Int, c: Char): Long = if (i == n) 1L else
dp.getOrPut(i to c) { vs[c]!!.sumOf { dfs(i + 1, it) } } %
1_000_000_007L
return dfs(0, '.').toInt()
}
Iterative version Another one-liner
5. Longest Palindromic Substring medium blog post substack Golf version
https://t.me/leetcode_daily_unstoppable/383
Longest palindrome substring
If dp[from][to]
answering whether substring s(from, to)
is a palindrome, then dp[from][to] = s[from] == s[to] && dp[from + 1][to - 1]
dp
array to avoid some corner cases checks.substring
each time.Time complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\)
fun longestPalindrome(s: String): String {
val dp = Array(s.length) { i -> BooleanArray(s.length) { i >= it } }
var res = s.take(1)
for (to in s.indices) for (from in to - 1 downTo 0) {
dp[from][to] = s[from] == s[to] && dp[from + 1][to - 1]
if (dp[from][to] && to - from + 1 > res.length)
res = s.substring(from, to + 1)
}
return res
}
823. Binary Trees With Factors medium blog post substack
https://t.me/leetcode_daily_unstoppable/382
Number of trees from arr
where each k
node has i
and j
leafs arr[k]=arr[j]*arr[i]
By naive intuition we can walk array in n^2
manner and collect all the matching multiplications. However, there is a nested depth, and we need a law how to add this to the result.
Let’s observe the pattern:
// 12 3 4 6 2
// 2x3=6 a
// 3x2=6 b
// 3x4=12 c
// 4x3=12 d
// 2x6=12 e
// 6x2=12 f
// 2x2=4 g
// 5 + [a b c d e f g] + [ca] + [da] + [ea eb] + [fa fb] = 18
If we start from node e
we must include both a
and b
. The equation becomes: f(k)=SUM(f(i)*f(j))
. For node e
: k=12, i=2, j=6, f(12)=f(2)*f(6), f(6)=f(3)*f(2) + f(2)*f(3)=2
If we sort the array, we will make sure, lower values are calculated.
We can think about this like a graph: 2x3->6->12
Calculate each array values individually using DFS + memo, then sum.
Time complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\)
fun numFactoredBinaryTrees(arr: IntArray): Int {
var set = arr.toSet()
arr.sort()
val dp = mutableMapOf<Int, Long>()
fun dfs(a: Int): Long = dp.getOrPut(a) {
1L + arr.sumOf {
if (a % it == 0 && set.contains(a / it))
dfs(it) * dfs(a / it) else 0L
}
}
return (arr.sumOf { dfs(it) } % 1_000_000_007L).toInt()
}
779. K-th Symbol in Grammar medium blog post substack
https://t.me/leetcode_daily_unstoppable/381
Binary Tree 0 -> 01
, 1 -> 10
at [n][k]
position
Let’s draw the example and see the pattern:
//1 [0]
//2 [0] 1
//3 [0] 1 1 0
//4 0 [1] 1 0 1 0 0 1
//5 0 1 1 [0] 1 0 0 1 1 0 0 1 0 1 1 0
//6 0 1 1 0 1 0 [0]1 1 0 0 1 0 1 1 0 1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1
// 1 2 3 4 5 6 7 8 9
// ^
Some observations:
0
starts its own tree, and every 1
start its own pattern of a tree.(k + 1) / 2
0
, current pair is 01
, otherwise 10
and 1
bit operation instead of % 2
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun kthGrammar(n: Int, k: Int): Int = if (n == 1) 0 else
(if (kthGrammar(n - 1, (k + 1) / 2) == 0) k.inv() else k) and 1
515. Find Largest Value in Each Tree Row medium blog post substack
https://t.me/leetcode_daily_unstoppable/380
Binary Tree’s maxes of the levels
Just use Breadth-First Search
Let’s use some Kotlin’s API:
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun largestValues(root: TreeNode?): List<Int> =
with(ArrayDeque<TreeNode>()) {
root?.let { add(it) }
generateSequence { if (isEmpty()) null else
(1..size).maxOf {
with(removeFirst()) {
left?.let { add(it) }
right?.let { add(it) }
`val`
}
}
}.toList()
}
342. Power of Four easy blog post substack
https://t.me/leetcode_daily_unstoppable/379
Is n == x^4
?
There are several ways to solve this. We need to look at the bit representation of some examples, there are an even number of trailing zeros and always just a single 1
bit:
// 4 100
// 16 10000
// 64 1000000
if (n == 1) true else if (n == 0) false
else n % 4 == 0 && isPowerOfFour(n / 4)
Bit shift approach:
var x = n
var count = 0
while (x > 0 && x and 1 == 0) {
x = x shr 1
count++
}
return x == 1 && count % 2 == 0
Bit mask approach:
n > 0 && (n and (n - 1)) == 0 && (n and 0b0101_0101_0101_0101__0101_0101_0101_0101 != 0)
Use Kotlin countTrailingZeroBits
. Or do a Binary Search if you write that algorithm by hand:
unsigned int c = 32; // c will be the number of zero bits on the right
v &= -signed(v);
if (v) c--;
if (v & 0x0000FFFF) c -= 16;
if (v & 0x00FF00FF) c -= 8;
if (v & 0x0F0F0F0F) c -= 4;
if (v & 0x33333333) c -= 2;
if (v & 0x55555555) c -= 1;
Time complexity: \(O(1)\), for bit mask solution
Space complexity: \(O(1)\)
fun isPowerOfFour(n: Int): Boolean = n > 0 &&
(n and (n - 1)) == 0 && n.countTrailingZeroBits() % 2 == 0
1793. Maximum Score of a Good Subarray hard blog post substack
https://t.me/leetcode_daily_unstoppable/378
Max of window_min * (window_size)
for window having nums[k]
This is not a problem where you need to find a minimum of a sliding window.
By description, we must always include nums[k]
. Let’s start from here and try to optimally add numbers to the left and to the right of it.
while (i > 0 && j < nums.lastIndex) ...
while (i > 0) ...
while (j < nums.lastIndex) ...
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun maximumScore(nums: IntArray, k: Int): Int {
var i = k
var j = k
var min = nums[k]
return generateSequence { when {
i == 0 && j == nums.lastIndex -> null
i > 0 && j < nums.lastIndex -> if (nums[i - 1] > nums[j + 1]) --i else ++j
i > 0 -> --i else -> ++j
} }.maxOfOrNull {
min = min(min, nums[it])
min * (j - i + 1)
} ?: nums[k]
}
1425. Constrained Subsequence Sum hard blog post substack
https://t.me/leetcode_daily_unstoppable/377
Max sum of subsequence i - j <= k
The naive DP approach is to do DFS and memoization:
fun constrainedSubsetSum(nums: IntArray, k: Int): Int {
val dp = mutableMapOf<Int, Int>()
fun dfs(i: Int): Int = if (i >= nums.size) 0 else dp.getOrPut(i) {
var max = nums[i]
for (j in 1..k) max = max(max, nums[i] + dfs(i + j))
max
}
return (0..<nums.size).maxOf { dfs(it) }
}
This solution takes O(nk) time and gives TLE.
Let’s rewrite it to the iterative version to think about further optimization:
fun constrainedSubsetSum(nums: IntArray, k: Int): Int {
val dp = mutableMapOf<Int, Int>()
for (i in nums.indices)
dp[i] = nums[i] + (i - k..i).maxOf { dp[it] ?: 0 }
return dp.values.max()
}
Next, read a hint :) It will suggest to use a Heap. Indeed, looking at this code, we’re just choosing a maximum value from the last k
values:
fun constrainedSubsetSum(nums: IntArray, k: Int): Int =
with (PriorityQueue<Int>(reverseOrder())) {
val dp = mutableMapOf<Int, Int>()
for (i in nums.indices) {
if (i - k > 0) remove(dp[i - k - 1])
dp[i] = nums[i] + max(0, peek() ?: 0)
add(dp[i])
}
dp.values.max()
}
This solution takes O(nlog(k)) time and still gives TLE.
Let’s look at other’s people solutions, just take a hint: decreasing queue
. This technique must be remembered, as it is a common trick to find a maximum in a sliding window with O(1) time.
Decreasing queue
flushes all the values that smaller than the current.
k
Time complexity: \(O(n)\)
Space complexity: \(O(k)\)
fun constrainedSubsetSum(nums: IntArray, k: Int): Int =
with (ArrayDeque<Int>()) {
for (i in nums.indices) {
if (isNotEmpty() && first() < i - k) removeFirst()
if (isNotEmpty()) nums[i] += max(0, nums[first()])
while (isNotEmpty() && nums[last()] < nums[i]) removeLast()
addLast(i)
}
nums.max()
}
341. Flatten Nested List Iterator medium blog post substack
https://t.me/leetcode_daily_unstoppable/376
Implement graph iterator
We need to save all the deep levels positions, so let’s use a Stack.
nextInt
integer in a separate variable, or just leave it in a Stack and do pop
on next()
advance
after each next()
call to know if there is a next positionTime complexity: \(O(n)\)
Space complexity: \(O(n)\)
class NestedIterator(nestedList: List<NestedInteger>) : Stack<NestedInteger>() {
init {
addAll(nestedList.reversed())
advance()
}
fun advance() {
while (isNotEmpty() && !peek().isInteger()) {
addAll(pop().list.reversed())
}
}
fun next(): Int = pop().integer.also { advance() }
fun hasNext(): Boolean = isNotEmpty()
}
844. Backspace String Compare medium blog post substack
https://t.me/leetcode_daily_unstoppable/375
Are typing with backspace
sequences equal
We can use a Stack to evaluate the resulting strings. However, scanning from the end and counting backspaces would work better.
Remove all of the backspaced chars before comparing
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun backspaceCompare(s: String, t: String): Boolean {
var si = s.lastIndex
var ti = t.lastIndex
while (si >= 0 || ti >= 0) {
var bs = 0
while (si >= 0 && (s[si] == '#' || bs > 0))
if (s[si--] == '#') bs++ else bs--
bs = 0
while (ti >= 0 && (t[ti] == '#' || bs > 0))
if (t[ti--] == '#') bs++ else bs--
if (si < 0 != ti < 0) return false
if (si >= 0 && s[si--] != t[ti--]) return false
}
return true
}
2050. Parallel Courses III hard blog post substack
https://t.me/leetcode_daily_unstoppable/374
Shortest time
to visit all nodes in relations=[from, to]
graph
We can start from nodes without out
siblings - leafs and do Depth-First Search from them, calculating time for each sibling in parallel and choosing the maximum. That is an optimal way to visit all the nodes. For each node, a solution can be cached.
Let’s use some Kotlin’s API:
from
nodes from all the nodes 1..n
Map<Int, List<Int>>
by using groupBy
maxOf
getOrPut
Time complexity: \(O(nr)\), will visit each node only once, r - average siblings count for each node
Space complexity: \(O(n)\)
fun minimumTime(n: Int, relations: Array<IntArray>, time: IntArray): Int {
val lastNodes = (1..n) - relations.map { it[0] }
val fromTo = relations.groupBy({ it[1] }, { it[0] })
val cache = mutableMapOf<Int, Int>()
fun dfs(curr: Int): Int = cache.getOrPut(curr) {
time[curr - 1] + (fromTo[curr]?.maxOf { dfs(it) } ?: 0)
}
return lastNodes.maxOf { dfs(it) }
}
P.S.: we can also just choose the maximum, as it will be the longest path:
fun minimumTime(n: Int, relations: Array<IntArray>, time: IntArray): Int {
val fromTo = relations.groupBy({ it[1] }, { it[0] })
val cache = mutableMapOf<Int, Int>()
fun dfs(curr: Int): Int = cache.getOrPut(curr) {
time[curr - 1] + (fromTo[curr]?.maxOf { dfs(it) } ?: 0)
}
return (1..n).maxOf { dfs(it) }
}
1361. Validate Binary Tree Nodes medium blog post substack
https://t.me/leetcode_daily_unstoppable/373
Is Binary Tree of leftChild[]
& rightChild[]
valid
There are some examples:
Tree is valid if:
For connections check let’s use Union-Find. We also must count in nodes.
Time complexity: \(O(an)\), a - is for Union-Find search, it is less than 10 for Int.MAX_VALUE nodes, if we implement ranks in Union-Find
Space complexity: \(O(n)\)
fun validateBinaryTreeNodes(n: Int, leftChild: IntArray, rightChild: IntArray): Boolean {
val uf = IntArray(n) { it }
val indeg = IntArray(n)
fun root(x: Int): Int = if (x == uf[x]) x else root(uf[x])
fun connect(a: Int, b: Int): Boolean {
if (b < 0) return true
if (indeg[b]++ > 0) return false
val rootA = root(a)
val rootB = root(b)
uf[rootA] = rootB
return rootA != rootB
}
return (0..<n).all {
connect(it, leftChild[it]) && connect(it, rightChild[it])
} && (0..<n).all { root(0) == root(it) }
}
119. Pascal’s Triangle II easy blog post substack
https://t.me/leetcode_daily_unstoppable/372
Pascal’s Triangle
One way is to generate sequence:
fun getRow(rowIndex: Int): List<Int> =
generateSequence(listOf(1)) {
listOf(1) + it.windowed(2) { it.sum() } + 1
}.elementAtOrElse(rowIndex) { listOf() }
Another way is to use fold
1
to collection by +
sum
and windowed
Time complexity: \(O(n^2)\)
Space complexity: \(O(n)\)
fun getRow(rowIndex: Int): List<Int> =
(1..rowIndex).fold(listOf(1)) { r, _ ->
listOf(1) + r.windowed(2) { it.sum() } + 1
}
1269. Number of Ways to Stay in the Same Place After Some Steps hard blog post substack
https://t.me/leetcode_daily_unstoppable/371
Number of ways to return to 0
after moving left, right
or stay
steps
time
We can do a brute force Depth First Search, each time moving position to the left
, right
or stay, adjusting steps
left. After all the steps used, we count the way if it is at 0
position.
The result will only depend on the inputs, so can be cached.
when
instead of if - else
, because you can forget else
:if (some) 0L
if (other) 1L // must be `else if`
Time complexity: \(O(s^2)\), max index can be no more than number of steps, as we move by 1 at a time
Space complexity: \(O(s^2)\)
fun numWays(steps: Int, arrLen: Int): Int {
val m = 1_000_000_007L
val dp = mutableMapOf<Pair<Int, Int>, Long>()
fun dfs(i: Int, s: Int): Long = dp.getOrPut(i to s) { when {
s == steps && i == 0 -> 1L
i < 0 || i >= arrLen || s >= steps -> 0L
else -> {
val leftRight = (dfs(i - 1, s + 1) + dfs(i + 1, s + 1)) % m
val stay = dfs(i, s + 1)
(leftRight + stay) % m
}
} }
return dfs(0, 0).toInt()
}
2742. Painting the Walls hard blog post substack
https://t.me/leetcode_daily_unstoppable/369
Min cost to complete all tasks using one paid cost[]
& time[]
and one free 0
& 1
workers
Let’s use a Depth First Search and try each wall by free
and by paid
workers.
After all the walls taken, we see if it is a valid combination: if paid
worker time less than free
worker time, then free worker dares to take task before paid worker, so it is invalid. We will track the time
, keeping it around zero: if free worker takes a task, time flies back, otherwise time goes forward by paid worker request. The valid combination is t >= 0
.
fun dfs(i: Int, t: Int): Int = dp.getOrPut(i to t) {
if (i == cost.size) { if (t < 0) 1_000_000_000 else 0 }
else {
val takePaid = cost[i] + dfs(i + 1, t + time[i])
val takeFree = dfs(i + 1, t - 1)
min(takePaid, takeFree)
}
}
This solution almost works, however gives TLE, so we need another trick min(cost.size, t + time[i])
:
1
point of time that is, can paint all the walls by n
points of time.n
points it’s over, we can use free worker, or basically we’re done.7 6 5 4 3 2 1
. If paid worker takes task with time 7
, all the other tasks will be left for free worker, because he is doing them by 1
points of time.Array
for the cache, but code becomes complexTime complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\)
fun paintWalls(cost: IntArray, time: IntArray): Int {
val dp = mutableMapOf<Int, Int>()
fun dfs(i: Int, t: Int): Int = dp.getOrPut((i shl 16) + t) {
if (i == cost.size) { if (t < 0) 1_000_000_000 else 0 }
else {
val takePaid = cost[i] + dfs(i + 1, min(cost.size, t + time[i]))
val takeFree = dfs(i + 1, t - 1)
min(takePaid, takeFree)
}
}
return dfs(0, 0)
}
746. Min Cost Climbing Stairs easy blog post substack
https://t.me/leetcode_daily_unstoppable/368
Classic DP: climbing stairs
Start with brute force approach: consider every position and choose one of a two - use current stair or use next. Given that, the result will only depend on the input position, so can be cached. This will give a simple DFS + memo DP code:
fun minCostClimbingStairs(cost: IntArray): Int {
val dp = mutableMapOf<Int, Int>()
fun dfs(curr: Int): Int = dp.getOrPut(curr) {
if (curr >= cost.lastIndex) 0
else min(
cost[curr] + dfs(curr + 1),
cost[curr + 1] + dfs(curr + 2)
)
}
return dfs(0)
}
This is accepted, but can be better if rewritten to bottom up and optimized.
After rewriting the recursive solution to iterative bottom up, we can notice, that only two
of the previous values are always used. Convert dp array into two variables.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun minCostClimbingStairs(cost: IntArray): Int {
var curr = 0
var prev = 0
for (i in 0..<cost.lastIndex)
curr = min(cost[i + 1] + curr, cost[i] + prev)
.also { prev = curr }
return curr
}
1095. Find in Mountain Array hard blog post substack
https://t.me/leetcode_daily_unstoppable/367
Binary Search in a mountain
First, find the top of the slope. Next, do two Binary Searches on the left and on the right slopes
For better Binary Search code
lo
and hi
lo == hi
top = max(top, mid)
lo = mid + 1
, hi = mid - 1
Time complexity: \(O(log(n))\)
Space complexity: \(O(1)\)
fun findInMountainArray(target: Int, mountainArr: MountainArray): Int {
var lo = 1
var hi = mountainArr.length() - 1
var top = -1
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (mountainArr.get(mid - 1) < mountainArr.get(mid)) {
top = max(top, mid)
lo = mid + 1
} else hi = mid - 1
}
lo = 0
hi = top
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
val m = mountainArr.get(mid)
if (m == target) return mid
if (m < target) lo = mid + 1 else hi = mid - 1
}
lo = top
hi = mountainArr.length() - 1
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
val m = mountainArr.get(mid)
if (m == target) return mid
if (m < target) hi = mid - 1 else lo = mid + 1
}
return -1
}
2251. Number of Flowers in Full Bloom hard blog post substack
https://t.me/leetcode_daily_unstoppable/366
Array of counts of segments in intersection
We need to quickly count how many segments are for any particular time. If we sort segments by from
position, we can use line sweep, and we also need to track times when count decreases.
To find out how many people
in a time range we can sort them and use Binary Search.
delta
s in timeToDelta
HashMapto + 1
TreeMap
people
s order, so use separate sorted indices
collectionFor better Binary Search code:
lo
and hi
lo == hi
peopleIndBefore = max(.., mid)
lo = mid + 1
, hi = mid - 1
mid
is less than target
drop everything on the left side: lo = mid + 1
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun fullBloomFlowers(flowers: Array<IntArray>, people: IntArray): IntArray {
val peopleInds = people.indices.sortedBy { people[it] }
val timeToDelta = TreeMap<Int, Int>()
for ((from, to) in flowers) {
timeToDelta[from] = 1 + (timeToDelta[from] ?: 0)
timeToDelta[to + 1] = -1 + (timeToDelta[to + 1] ?: 0)
}
val res = IntArray(people.size)
var count = 0
var lastPeopleInd = -1
timeToDelta.onEach { (time, delta) ->
var lo = max(0, lastPeopleInd - 1)
var hi = peopleInds.lastIndex
var peopleIndBefore = -1
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (people[peopleInds[mid]] < time) {
peopleIndBefore = max(peopleIndBefore, mid)
lo = mid + 1
} else hi = mid - 1
}
for (i in max(0, lastPeopleInd)..peopleIndBefore) res[peopleInds[i]] = count
count += delta
lastPeopleInd = peopleIndBefore + 1
}
return res
}
2009. Minimum Number of Operations to Make Array Continuous hard blog post substack
https://t.me/leetcode_daily_unstoppable/365
Min replacements to make array continuous a[i] = a[i - 1] + 1
Use hint. There are some ideas to solve this:
1 3 4 -> 1 2 3 or 3 4 5 or 4 5 6
1 3 4
, if current number is 1
we drop all numbers bigger than 3
as 1 2 3
is a result.duplicates
. To count how many duplicates in range in O(1) we can precompute a prefix counter of the unique numbers.Look at someone else’s solution. For better Binary Search code:
lo
and hi
lo == hi
lo = mid + 1
, hi = mid - 1
toPos = min(toPos, mid)
mid
position: if nums[mid] is less than target, we can drop all numbers to the left, so move lo
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun minOperations(nums: IntArray): Int {
nums.sort()
val uniqPrefix = IntArray(nums.size) { 1 }
for (i in 1..<nums.size) {
uniqPrefix[i] = uniqPrefix[i - 1]
if (nums[i] != nums[i - 1]) uniqPrefix[i]++
}
var minOps = nums.size - 1
for (i in nums.indices) {
val from = nums[i]
val to = from + nums.size - 1
var lo = i
var hi = nums.size - 1
var toPos = nums.size
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (nums[mid] > to) {
toPos = min(toPos, mid)
hi = mid - 1
} else lo = mid + 1
}
val uniqCount = max(0, uniqPrefix[toPos - 1] - uniqPrefix[i]) + 1
minOps = min(minOps, nums.size - uniqCount)
}
return minOps
}
34. Find First and Last Position of Element in Sorted Array medium blog post substack
https://t.me/leetcode_daily_unstoppable/364
Binary Search range
Just write a Binary Search
For simpler code:
lo
and hi
lo == hi
lo = mid + 1
, hi = mid - 1
if (nums[mid] == target)
if this position is definitely less than target, we can drop it and all that less than it
Time complexity: \(O(log(n))\)
Space complexity: \(O(1)\)
fun searchRange(nums: IntArray, target: Int): IntArray {
var from = -1
var lo = 0
var hi = nums.lastIndex
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (nums[mid] == target) from = min(max(from, nums.size), mid)
if (nums[mid] < target) lo = mid + 1
else hi = mid - 1
}
var to = from
lo = maxOf(0, from)
hi = nums.lastIndex
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (nums[mid] == target) to = max(min(-1, to), mid)
if (nums[mid] <= target) lo = mid + 1
else hi = mid - 1
}
return intArrayOf(from, to)
}
1458. Max Dot Product of Two Subsequences hard blog post substack
https://t.me/leetcode_daily_unstoppable/363
Max product of two subsequences
We can search in all possible subsequences in O(n^2) by choosing between: take element and stop, take and continue, skip first, skip second.
The top-down aproach is trivial, let’s modify it into bottom up.
dp
size to avoid writing if
sTime complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\)
fun maxDotProduct(nums1: IntArray, nums2: IntArray): Int {
val dp = Array(nums1.size + 1) { Array(nums2.size + 1) { -1000000 } }
for (j in nums2.lastIndex downTo 0)
for (i in nums1.lastIndex downTo 0)
dp[i][j] = maxOf(
nums1[i] * nums2[j],
nums1[i] * nums2[j] + dp[i + 1][j + 1],
dp[i][j + 1],
dp[i + 1][j])
return dp[0][0]
}
1420. Build Array Where You Can Find The Maximum Exactly K Comparisons hard blog post substack
https://t.me/leetcode_daily_unstoppable/362
Count possible arrays of n 1..m
values increasing k
times
First, try to write down some examples of arrays. There are some laws of how the number of arrays grows.
Next, use hint :)
Then just write Depth First Search of all possible numbers for each position and count how many times numbers grows. Stop search when it is bigger than k
times. The result can be cached.
Time complexity: \(O(nkm^2)\), nkm - is a search depth, and another m for internal loop
Space complexity: \(O(nkm)\)
fun numOfArrays(n: Int, m: Int, k: Int): Int {
val mod = 1_000_000_007L
val dp = Array(n) { Array(m + 1) { Array(k + 1) { -1L } } }
fun dfs(i: Int, max: Int, c: Int): Long =
if (c > k) 0L
else if (i == n) { if (c == k) 1L else 0L }
else dp[i][max][c].takeIf { it >= 0 } ?: {
var sum = (max * dfs(i + 1, max, c)) % mod
for (x in (max + 1)..m)
sum = (sum + dfs(i + 1, x, c + 1)) % mod
sum
}().also { dp[i][max][c] = it }
return dfs(0, 0, 0).toInt()
}
343. Integer Break medium blog post substack
https://t.me/leetcode_daily_unstoppable/361
Max multiplication of the number split
We can search from all possible splits. The result will only depend on the input n
, so can be cached.
2, 3, 4
: ensure there is at least one split happenTime complexity:
\(O(n^2)\), recursion depth is n
and another n
is in the loop. Without cache, it would be n^n
Space complexity: \(O(n)\)
val cache = mutableMapOf<Int, Int>()
fun integerBreak(n: Int, canTake: Boolean = false): Int =
if (n == 0) 1 else cache.getOrPut(n) {
(1..if (canTake) n else n - 1).map {
it * integerBreak(n - it, true)
}.max()
}
229. Majority Element II medium blog post substack
https://t.me/leetcode_daily_unstoppable/360
Elements with frequency > size / 3
The naive solution, which is to count frequencies, can be this one-liner:
fun majorityElement(nums: IntArray) = nums
.groupBy { it }
.filter { (k, v) -> v.size > nums.size / 3 }
.map { (k, v) -> k }
However, to solve it in O(1) we need to read the hint
: Moore algo.
One idea is that there are at most only two
such elements can coexist:
// 111 123 333
// 1111 1234 4444
// 11111 12345 55555
The second idea is a clever counting of three
buckets: first
candidate, second
candidate and others. We decrease candidates counters if x
in the others
bucket, and change candidate if it’s counter 0
.
Steal someone’s else solution or ask ChatGPT about Moore
algorithm to find majority element.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun majorityElement(nums: IntArray): List<Int> {
var x1 = Int.MIN_VALUE
var x2 = Int.MIN_VALUE
var count1 = 0
var count2 = 0
for (x in nums) when {
x != x2 && count1 == 0 -> x1 = x.also { count1 = 1 }
x != x1 && count2 == 0 -> x2 = x.also { count2 = 1 }
x == x1 -> count1++
x == x2 -> count2++
else -> {
count1 = maxOf(0, count1 - 1)
count2 = maxOf(0, count2 - 1)
}
}
return buildList {
if (nums.count { it == x1 } > nums.size / 3) add(x1)
if (nums.count { it == x2 } > nums.size / 3) add(x2)
}
}
706. Design HashMap easy blog post substack
https://t.me/leetcode_daily_unstoppable/359
Design a HashMap
The simple implementation consists of a growing array of buckets, where each bucket is a list of key-value pairs.
For better performance:
LinkedList
Time complexity: \(O(1)\)
Space complexity: \(O(1)\), for all operations
class MyHashMap() {
var table = Array<MutableList<Pair<Int, Int>>>(16) { mutableListOf() }
var count = 0
fun bucket(key: Int) = table[key % table.size]
fun rehash() = with(table.flatMap { it }) {
table = Array(table.size * 2) { mutableListOf() }
for ((key, value) in this) bucket(key) += key to value
}
fun put(key: Int, value: Int) = with(bucket(key)) {
if (removeAll { it.first == key }) count++
this += key to value
if (count > table.size) rehash()
}
fun get(key: Int) = bucket(key)
.firstOrNull { it.first == key }?.second ?: -1
fun remove(key: Int) {
if (bucket(key).removeAll { it.first == key }) count--
}
}
1512. Number of Good Pairs easy blog post substack
https://t.me/leetcode_daily_unstoppable/358
Count equal pairs
The naive N^2 solution will work.
Another idea is to store the number frequency
so far and add it to the current result.
Let’s use Kotlin’s API:
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun numIdenticalPairs(nums: IntArray) = with(IntArray(101)) {
nums.fold(0) { r, t -> r + this[t].also { this[t]++ } }
}
2038. Remove Colored Pieces if Both Neighbors are the Same Color medium blog post substack
https://t.me/leetcode_daily_unstoppable/357
Is A
wins in middle-removing AAA
or BBB
game
We quickly observe, that removing A
in BBAAABB
doesn’t make B
turn possible, so the outcome does not depend on how exactly positions are removed. A
can win if it’s possible game turns are more than B
. So, the problem is to find how many consequent A
’s and B
’s are.
We can count A
and B
in a single pass, however, let’s write a two-pass one-liner using window
Kotlin method.
Time complexity: \(O(n)\)
Space complexity:
\(O(n)\), can be O(1) if asSequence
used
fun winnerOfGame(colors: String) = with(colors.windowed(3)) {
count { it.all { it == 'A' } } > count { it.all { it == 'B' } }
}
557. Reverse Words in a String III easy blog post substack
https://t.me/leetcode_daily_unstoppable/356
Reverse words
In an interview in-place solution expected. Maintain two pointers, and adjust one until end of word reached. This still takes O(N) space in JVM.
Let’s write a one-liner using Kotlin’s API
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun reverseWords(s: String) =
s.reversed().split(" ").reversed().joinToString(" ")
456. 132 Pattern medium blog post substack
https://t.me/leetcode_daily_unstoppable/355
132
pattern in array
If we slide the array from behind, we simplify the task to find the smallest element. When searching for largest decreasing subsequence we can use a monotonic Stack.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun find132pattern(nums: IntArray): Boolean {
val stack = Stack<Int>()
var lo = Int.MIN_VALUE
return (nums.lastIndex downTo 0).any { i ->
while (stack.isNotEmpty() && stack.peek() < nums[i]) lo = stack.pop()
stack.push(nums[i])
nums[i] < lo
}
}
896. Monotonic Array easy blog post substack
https://t.me/leetcode_daily_unstoppable/354
Is array monotonic
Let’s compute the diffs, then array is monotonic if all the diffs have the same sign.
Let’s use Kotlin’s API:
x
sized sliding windowTime complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun isMonotonic(nums: IntArray) =
nums.asSequence().windowed(2)
.map { it[0] - it[1] }
.filter { it != 0 }
.windowed(2)
.all { it[0] > 0 == it[1] > 0 }
905. Sort Array By Parity easy blog post substack
https://t.me/leetcode_daily_unstoppable/353
Sort an array by even-odd
There are built-in functions. However, in an interview manual partition is expected: maintain the sorted border l
and adjust it after swapping.
Let’s write them all.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
// 1
fun sortArrayByParity(nums: IntArray) = nums.also {
var l = 0
for (r in nums.indices) if (nums[r] % 2 == 0)
nums[r] = nums[l].also { nums[l++] = nums[r] }
}
// 2
fun sortArrayByParity(nums: IntArray) =
nums.partition { it % 2 == 0 }.toList().flatten()
// 3
fun sortArrayByParity(nums: IntArray) = nums.sortedBy { it % 2 }
880. Decoded String at Index medium blog post substack
https://t.me/leetcode_daily_unstoppable/352
k
-th character in an encoded string like a3b2=aaabaaab
We know the resulting length at every position of the encoded string. For example,
a3b2
1348
The next step, just walk from the end of the string and adjust k
, by undoing repeating operation:
// a2b2c2
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13
// a a b a a b c a a b a a b c
// a2b2c2 = 2 x a2b2c = 2*(a2b2 + c) =
// 2*(2*(a2 + b) + c) = 2*(2*(2*a + b) + c)
// k=9 9%(len(a2b2c)/2)
//
// a3b2 k=7
// 12345678
// aaabaaab
// aaab k=7%4=3
//
// abcd2 k=6
// 12345678
// abcdabcd k%4=2
isDigit
digitToInt
search
is become
0`, we must return first non-digit characterTime complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun decodeAtIndex(s: String, k: Int): String {
val lens = LongArray(s.length) { 1L }
for (i in 1..s.lastIndex) lens[i] = if (s[i].isDigit())
lens[i - 1] * s[i].digitToInt()
else lens[i - 1] + 1
var search = k.toLong()
for (i in s.lastIndex downTo 0) if (s[i].isDigit())
search = search % (lens[i] / s[i].digitToInt().toLong())
else if (lens[i] == search || search == 0L) return "" + s[i]
throw error("not found")
}
316. Remove Duplicate Letters medium blog post substack
https://t.me/leetcode_daily_unstoppable/351
Lexicographical smallest subsequence without duplicates
The brute force way would be to just consider every position and do a DFS. To pass the test case, however, there is a greedy way: let’s take characters and pop them if new is smaller and the duplicate exists later in a string.
// 01234
// 234
// bcabc
// * b
// * bc
// * a, pop c, pop b
// * ab
// * abc
We can use Kotlin’s buildString
API instead of a Stack
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun removeDuplicateLetters(s: String) = buildString {
var visited = mutableSetOf<Char>()
val lastInds = mutableMapOf<Char, Int>()
s.onEachIndexed { i, c -> lastInds[c] = i}
s.onEachIndexed { i, c ->
if (visited.add(c)) {
while (isNotEmpty() && last() > c && i < lastInds[last()]!!)
visited.remove(last()).also { setLength(lastIndex) }
append(c)
}
}
}
389. Find the Difference easy blog post substack
https://t.me/leetcode_daily_unstoppable/350
Strings difference by a single char
We can use frequency map. Or just calculate total sum by Char Int value.
Let’s use Kotlin’s API sumBy
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun findTheDifference(s: String, t: String) =
(t.sumBy { it.toInt() } - s.sumBy { it.toInt() }).toChar()
799. Champagne Tower medium blog post substack
https://t.me/leetcode_daily_unstoppable/349
Positional flow value in a Pascal’s Triangle
Let’s treat every glass value as the total flow passed through it. Otherwise, it is a standard Pascal’s Triangle problem: reuse the previous row to compute the next.
1.0
(full), it will contribute 0.0
to the next row. This can be written as max(0, x - 1)
Time complexity: \(O(n^2)\)
Space complexity: \(O(n)\)
fun champagneTower(poured: Int, query_row: Int, query_glass: Int): Double {
var flow = listOf(poured.toDouble())
repeat(query_row) {
val middle = flow.windowed(2).map { (a, b) ->
max(0.0, a - 1.0) / 2 + max(0.0, b - 1.0) / 2
}
val edge = listOf(maxOf(0.0, flow.first() - 1.0) / 2)
flow = edge + middle + edge
}
return minOf(flow[query_glass], 1.0)
}
1048. Longest String Chain medium blog post substack
https://t.me/leetcode_daily_unstoppable/348
Longest chain of words with single character added
We can build a graph, then use DFS to find a maximum depth. To detect predecessor, we can use two pointers.
Careful with two pointers: iterate over short string and adjust the second pointer for long, not vice versa.
Time complexity: \(O(w*n^2)\), to build a graph
Space complexity: \(O(n^2)\), for graph
fun longestStrChain(words: Array<String>): Int {
fun isPred(a: String, b: String): Boolean {
if (a.length != b.length - 1) return false
var i = -1
return !a.any {
i++
while (i < b.length && it != b[i]) i++
i == b.length
}
}
val fromTo = mutableMapOf<String, MutableSet<String>>()
for (a in words)
for (b in words)
if (isPred(a, b))
fromTo.getOrPut(a) { mutableSetOf() } += b
val cache = mutableMapOf<String, Int>()
fun dfs(w: String): Int = cache.getOrPut(w) {
1 + (fromTo[w]?.map { dfs(it) }?.max() ?: 0)
}
return words.map { dfs(it) }?.max() ?: 0
}
392. Is Subsequence easy blog post substack
https://t.me/leetcode_daily_unstoppable/347
Is string a subsequence of another
One possible way is to build a Trie, however this problem can be solved just with two pointers.
Iterate over one string and adjust pointer of another.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun isSubsequence(s: String, t: String): Boolean {
var i = -1
return !s.any { c ->
i++
while (i < t.length && t[i] != c) i++
i == t.length
}
}
4. Median of Two Sorted Arrays hard blog post substack
https://t.me/leetcode_daily_unstoppable/346
Median in two concatenated sorted arrays
We already know the target position of the median element in the concatenated array.
There is an approach with Binary Search, but it’s harder to come up with in an interview and write correctly.
We can maintain two pointers and increase them one by one until targetPos
reached.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun findMedianSortedArrays(nums1: IntArray, nums2: IntArray): Double {
val targetPos = (nums1.size + nums2.size) / 2
var i = 0
var j = 0
var prev = 0
var curr = 0
while (i + j <= targetPos) {
prev = curr
curr = when {
i == nums1.size -> nums2[j++]
j == nums2.size -> nums1[i++]
nums1[i] <= nums2[j] -> nums1[i++]
else -> nums2[j++]
}
}
return if ((nums1.size + nums2.size) % 2 == 0)
(prev + curr) / 2.0
else
curr.toDouble()
}
1658. Minimum Operations to Reduce X to Zero medium blog post substack
https://t.me/leetcode_daily_unstoppable/345
Min suffix-prefix to make an x
We can reverse the problem: find the middle of the array to make an arr_sum() - x
. Now, this problem can be solved using a sliding window technique.
For more robust sliding window:
windowSize
variableTime complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun minOperations(nums: IntArray, x: Int): Int {
val targetSum = nums.sum() - x
var windowSize = 0
var currSum = 0
var res = Int.MAX_VALUE
nums.onEachIndexed { i, n ->
currSum += n
windowSize++
while (currSum > targetSum && windowSize > 0)
currSum -= nums[i - (windowSize--) + 1]
if (currSum == targetSum)
res = minOf(res, nums.size - windowSize)
}
return res.takeIf { it < Int.MAX_VALUE } ?: -1
}
287. Find the Duplicate Number medium blog post substack
https://t.me/leetcode_daily_unstoppable/344
Found duplicate in array, each value is in 1..<arr.size
Hint: 4 2 2 2 2 ... 2
is also the case.
What we can see, is that every value is in the 1..<arr.size
range, so we can temporarly store the flag in here, then revert it back in the end.
// 0 1 2 3 4 sz = 5
// 3 1 3 4 2
// 3 *
// 1 *
// 3 x
//
For a flag we can just add some big value to the number, or make it negative, for example.
Let’s write it using some Kotlin’s API:
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun findDuplicate(nums: IntArray) = nums.first { n ->
nums[n % nums.size] >= nums.size
.also { nums[n % nums.size] += nums.size }
} % nums.size
.also { for (j in nums.indices) nums[j] %= nums.size }
1337. The K Weakest Rows in a Matrix easy blog post substack
https://t.me/leetcode_daily_unstoppable/343
k
indices with smallest row sum in a binary matrix
We can precompute row sums, then use a Priority Queue to find k
smallest. However, just sorting all will also work.
Let’s use Kotlin’s collections API
Time complexity: \(O(n^2logn)\)
Space complexity: \(O(n^2)\)
fun kWeakestRows(mat: Array<IntArray>, k: Int) = mat
.map { it.filter { it == 1 }.sum() ?: 0 }
.withIndex()
.sortedBy { it.value }
.map { it.index }
.take(k)
.toIntArray()
1631. Path With Minimum Effort medium blog post substack
https://t.me/leetcode_daily_unstoppable/341
Minimum absolute difference in path top-left to right-bottom
To find an optimal path using some condition, we can use A* algorithm:
PriorityQueue
PQ
Time complexity: \(O(nmlog(nm))\)
Space complexity: \(O(nm)\)
val dirs = sequenceOf(1 to 0, 0 to 1, 0 to -1, -1 to 0)
fun minimumEffortPath(heights: Array<IntArray>): Int {
val pq = PriorityQueue<Pair<Pair<Int, Int>, Int>>(compareBy { it.second })
pq.add(0 to 0 to 0)
val visited = HashSet<Pair<Int, Int>>()
while (pq.isNotEmpty()) {
val (xy, diff) = pq.poll()
if (!visited.add(xy)) continue
val (x, y) = xy
if (x == heights[0].lastIndex && y == heights.lastIndex) return diff
dirs.map { (dx, dy) -> x + dx to y + dy }
.filter { (x1, y1) -> x1 in 0..<heights[0].size && y1 in 0..<heights.size }
.forEach { (x1, y1) -> pq.add(x1 to y1 to maxOf(diff, abs(heights[y][x] - heights[y1][x1]))) }
}
return 0
}
1584. Min Cost to Connect All Points medium blog post substack
https://t.me/leetcode_daily_unstoppable/340
Min manhatten distance connected graph
We can start from any points, for example, 0
. Next, we must iterate over all possible edges and find one with minimum distance
.
Priority Queue
to sort all edges by distanceTime complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\)
fun minCostConnectPoints(points: Array<IntArray>): Int {
fun dist(from: Int, to: Int) =
abs(points[from][0] - points[to][0]) + abs(points[from][1] - points[to][1])
val notVisited = points.indices.toMutableSet()
val pq = PriorityQueue<Pair<Int, Int>>(compareBy({ it.second }))
pq.add(0 to 0)
var sum = 0
while (notVisited.isNotEmpty()) {
val curr = pq.poll()
if (!notVisited.remove(curr.first)) continue
sum += curr.second
for (to in notVisited) pq.add(to to dist(curr.first, to))
}
return sum
}
332. Reconstruct Itinerary hard blog post substack
https://t.me/leetcode_daily_unstoppable/339
Smallest lexical order path using all the tickets
We can build a graph, then do DFS in a lexical order, backtracking. First path with all tickets used will be the answer.
Time complexity: \(O(x^n)\), where x - is an average edges count per node
Space complexity: \(O(n)\)
fun findItinerary(tickets: List<List<String>>): List<String> {
val fromTo = mutableMapOf<String, MutableList<Pair<Int, String>>>()
tickets.forEachIndexed { i, (from, to) ->
fromTo.getOrPut(from) { mutableListOf() } += i to to
}
for (list in fromTo.values) list.sortWith(compareBy { it.second })
val usedTickets = mutableSetOf<Int>()
var path = mutableListOf("JFK")
fun dfs(): List<String> =
if (usedTickets.size == tickets.size) path.toList()
else fromTo[path.last()]?.asSequence()?.map { (ind, next) ->
if (usedTickets.add(ind)) {
path.add(next)
dfs().also {
path.removeAt(path.lastIndex)
usedTickets.remove(ind)
}
} else emptyList()
}?.filter { it.isNotEmpty() }?.firstOrNull() ?: emptyList()
return dfs()
}
135. Candy hard blog post substack
https://t.me/leetcode_daily_unstoppable/338
Minimum candies count to satisfy condition: ratings[i] < ratings[i-1]
must give more candies to i-1
Let’s observe the example:
// 0 1 2 3 4 5 6 7 8
// 1 2 2 3 2 1 5 3 4
// 1 1 1 1 1 1 1 1 1
// 1 1 1 1 1
// 1
// 1 -> [0]
// 3 -> [2, 4]
// 6 -> [5, 7]
// 8 -> [7]
//
// 1 2 3 4 5 6 7 8 9
// 1 1 1 1 1 1 1 1 1
// 1 1 1 1 1 1 1 1
// 1 1 1 1 1 1 1
// 1 1 1 1 1 1
// 1 1 1 1 1
// 1 1 1 1
// 1 1 1
// 1 1
// 1
// 1 <- 2 <- 3 ...
We can look at this as a graph with nodes of siblings from higher rating to lower. Then the minimum number of candies is a maximum graph path length.
depth
value for each visited nodeTime complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun candy(ratings: IntArray): Int {
val fromTo = mutableMapOf<Int, MutableList<Int>>()
for (i in 1..<ratings.size)
if (ratings[i] > ratings[i - 1])
fromTo.getOrPut(i) { mutableListOf() } += i - 1
else if (ratings[i] < ratings[i -1])
fromTo.getOrPut(i - 1) { mutableListOf() } += i
val depth = IntArray(ratings.size)
fun maxDepth(curr: Int): Int =
depth[curr].takeIf { it > 0 } ?:
(1 + (fromTo[curr]?.map { maxDepth(it) }?.maxOrNull() ?: 0))
.also { depth[curr] = it }
return ratings.indices.sumBy { maxDepth(it) }
}
1647. Minimum Deletions to Make Character Frequencies Unique medium blog post substack
https://t.me/leetcode_daily_unstoppable/337
Minimum removes duplicate frequency chars from string
// b b c e b a b
// 1 1 1 4
Characters doesn’t matter, only frequencies. Let’s sort them and scan one-by-one from biggest to small and descriase max value.
Let’s use Kotlin collections API:
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun minDeletions(s: String): Int {
var prev = Int.MAX_VALUE
return s.groupBy { it }.values
.map { it.size }
.sortedDescending()
.sumBy {
prev = maxOf(0, minOf(it, prev - 1))
maxOf(0, it - prev)
}
}
1282. Group the People Given the Group Size They Belong To medium blog post substack
https://t.me/leetcode_daily_unstoppable/336
Groups from groups sizes array
First, group by sizes, next, chunk by groups size each.
Let’s write it using Kotlin collections API
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
// 2 1 3 3 3 2 1 1 1 2 2
// 0 1 2 3 4 5 6 7 8 9 10
// 2 -> 0 5 [9 10]
// 1 -> 1 [6] [7] [8]
// 3 -> 2 3 4
fun groupThePeople(groupSizes: IntArray) =
groupSizes
.withIndex()
.groupBy { it.value }
.flatMap { (sz, nums) ->
nums.map { it.index }.chunked(sz)
}
1359. Count All Valid Pickup and Delivery Options hard blog post substack
https://t.me/leetcode_daily_unstoppable/335
Count permutations of the n
pickup -> delivery
orders
Let’s look at how orders can be placed and draw the picture:
// 1: p1 d1 variantsCount = 1
// 2: length = 2
// "___p1____d1_____": vacantPlaces = 3
// p2 d2
// p2 d2
// p2 d2
// p2 d2
// p2 d2
// p2 d2
// variantsCount = 6
// 3: length = 4
// "___p1____d1____p2____d2____": vacantPlaces = 5
// p3 d3
// p3 d3
// p3 d3
// p3 d3
// p3 d3
// p3 d3
// p3 d3 x6
// p3 d3
// p3 d3
// p3 d3
// p3 d3
// p3 d3
// p3 d3
// p3 d3
// p3 d3
In this example, we can see the pattern:
2
each roundLong
to avoid overflowTime complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun countOrders(n: Int): Int {
var variantsCount = 1L
var currSum = 1L
var item = 1L
val m = 1_000_000_007L
repeat(n - 1) {
item = (item + 1L) % m
currSum = (currSum + item) % m
item = (item + 1L) % m
currSum = (currSum + item) % m
variantsCount = (variantsCount * currSum) % m
}
return variantsCount.toInt()
}
377. Combination Sum IV medium blog post substack
https://t.me/leetcode_daily_unstoppable/334
Number of ways to sum up array nums to target
This is a canonical DP knapsack problem: choose one of the items and decrease the target
by its value. If target
is zero - we have a single way, if negative - no ways, otherwise keep taking items. The result will only depend on the target
, so can be cached.
In this code:
0 -> 1, negative -> 0
: 1 - (t ushr 31)
, it shifts the leftmost bit to the right treating sign bit as a value bit, converting any negative number to 1
and positive to 0
IntArray
used instead of Map
using takeIf
Kotlin operatorTime complexity:
\(O(n^2)\), n
for the recursion depth, and n
for the inner iteration
Space complexity: \(O(n^2)\)
fun combinationSum4(nums: IntArray, target: Int): Int {
val cache = IntArray(target + 1) { -1 }
fun dfs(t: Int): Int = if (t <= 0) 1 - (t ushr 31) else
cache[t].takeIf { it >= 0 } ?:
nums.sumBy { dfs(t - it) }.also { cache[t] = it }
return dfs(target)
}
118. Pascal’s Triangle easy blog post substack
https://t.me/leetcode_daily_unstoppable/333
Pascal Triangle
Each row is a previous row sliding window sums concatenated with 1
Let’s write it using Kotlin API
Time complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\)
fun generate(numRows: Int) = (2..numRows)
.runningFold(listOf(1)) { r, _ ->
listOf(1) + r.windowed(2).map { it.sum() } + listOf(1)
}
92. Reverse Linked List II medium blog post substack
https://t.me/leetcode_daily_unstoppable/332
Reverse a part of Linked List
We need to find a point where to start reversing after left
steps, then do the reversing right - left
steps and finally connect to tail.
Dummy head
technique to avoid reversed head corner caseTime complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun reverseBetween(head: ListNode?, left: Int, right: Int): ListNode? {
val dummy = ListNode(0).apply { next = head }
var prev: ListNode? = dummy
var curr = prev // d-1-2-3-4-5 2 4
repeat(left) { // pc
prev = curr // p c
curr = curr?.next // p c
}
val head = prev // d-1-2-3-4-5 2 4
val tail = curr // h t
prev = curr
curr = curr?.next // p c
repeat(right - left) { // p c n
val next = curr?.next // <p c n
curr?.next = prev // p<c n
prev = curr // <p<c n
curr = next // 2<p c
} // 2<3<p c
head?.next = prev // d-1-2-3-4-5 2 4
tail?.next = curr // h t<3<p c
return dummy.next
}
725. Split Linked List in Parts medium blog post substack
https://t.me/leetcode_daily_unstoppable/331
Split Linked List
into k
almost equal lists
First, precompute sizes, by adding to buckets one-by-one in a loop. Next, just move list pointer by sizes values.
Do not forget to disconnect nodes.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\) for the sizes array and for the result
fun splitListToParts(head: ListNode?, k: Int): Array<ListNode?> {
val sizes = IntArray(k)
var i = 0
var curr = head
while (curr != null) {
sizes[i++ % k]++
curr = curr.next
}
curr = head
return sizes.map { sz ->
curr.also {
repeat(sz - 1) { curr = curr?.next }
curr = curr?.next.also { curr?.next = null }
}
}.toTypedArray()
}
138. Copy List with Random Pointer medium blog post substack
Copy of a graph
Simple way is just store mapping old -> new
.
The trick from hint is to store new nodes in between the old ones, then mapping became old -> new.next & new -> old.next
.
One iteration to make new nodes, second to assign random
field and final to split lists back.
fun copyRandomList(node: Node?): Node? {
var curr = node
while (curr != null) {
val next = curr.next
curr.next = Node(curr.`val`).apply { this.next = next }
curr = next
}
curr = node
while (curr != null) {
curr.next?.random = curr.random?.next
curr = curr.next?.next
}
curr = node
val head = node?.next
while (curr != null) {
val currNew = curr.next
val nextOrig = currNew?.next
val nextNew = nextOrig?.next
curr.next = nextOrig
currNew?.next = nextNew
curr = nextOrig
}
return head
}
141. Linked List Cycle easy blog post substack
Detect a cycle in a LinkedList
Use tortoise and rabbit technique
Move one pointer one step at a time, another two steps at a time. If there is a cycle, they will meet.
Time complexity: \(O(n)\)
Space complexity: \(O(log(n))\) for recursion (iterative version is O(1))
fun hasCycle(slow: ListNode?, fast: ListNode? = slow?.next): Boolean =
fast != null && (slow == fast || hasCycle(slow?.next, fast?.next?.next))
62. Unique Paths medium blog post substack
https://t.me/leetcode_daily_unstoppable/328
Unique paths count, moving right-down
from top-left
to bottom-right
On each cell, the number of paths is a sum of direct up
number and direct left
number.
Use single row array, as only previous up row is relevant
Time complexity: \(O(nm)\)
Space complexity: \(O(m)\)
fun uniquePaths(m: Int, n: Int): Int {
val row = IntArray(n) { 1 }
for (y in 1..<m)
for (x in 1..<n)
row[x] += row[x - 1]
return row.last()
}
2707. Extra Characters in a String medium blog post substack
https://t.me/leetcode_daily_unstoppable/327
Min count of leftovers after string split by the dictionary
We can search all possible splits at every position when we find a word. To quickly find a word, let’s use a Trie
. The result will only depend on the suffix of the string, so can be cached.
Do DFS, each time compare a skipped
result with any take_word
result, if found a word. We must continue to search, because some words can be prefixes to others: leet
, leetcode
-> leetcodes
, taking leet
is not optimal.
Time complexity:
\(O(n^2)\), DFS depth is n
and another n
for the inner iteration
Space complexity: \(O(n)\)
class Trie(var w: Boolean = false) : HashMap<Char, Trie>()
fun minExtraChar(s: String, dictionary: Array<String>): Int {
val trie = Trie()
for (w in dictionary) {
var t = trie
for (c in w) t = t.getOrPut(c) { Trie() }
t.w = true
}
val cache = mutableMapOf<Int, Int>()
fun dfs(pos: Int): Int = if (pos >= s.length) 0 else
cache.getOrPut(pos) {
var min = 1 + dfs(pos + 1)
var t = trie
for (i in pos..<s.length) {
t = t[s[i]] ?: break
if (t.w) min = minOf(min, dfs(i + 1))
}
min
}
return dfs(0)
}
338. Counting Bits easy blog post substack
https://t.me/leetcode_daily_unstoppable/326
Array of bits count for numbers 0..n
There is a tabulation technique used for caching bits count answer in O(1): for number xxxx0
bits count is count(xxxx) + 0
, but for number xxxx1
bits count is count(xxxx) + 1
. Now, to make a switch xxxx1 -> xxxx
simple divide by 2. Result can be cached.
We can use DFS + memo, but bottom-up also simple. Result is a DP array itself: DP[number] = bits_count(number)
. The last bit can be checked by %
operation, but and
also works.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun countBits(n: Int) = IntArray(n + 1).apply {
for (i in 0..n)
this[i] = this[i / 2] + (i and 1)
}
1326. Minimum Number of Taps to Open to Water a Garden hard blog post substack
https://t.me/leetcode_daily_unstoppable/325
Fill all space between 0..n
using minimum intervals
We need to fill space between points, so skip all zero intervals. Next, sort intervals and scan them greedily. Consider space between lo..hi
as filled. If from > lo
we must open another water source. However, there are possible good candidates before, if their to > hi
.
// 0 1 2 3 4 5 6 7 8 9
// 0 5 0 3 3 3 1 4 0 4
// 1 5 *************
// ^ ^
// lo hi
// 3 3 *************
// 4 3 *************
// ^ . ^
// from . to
// *** opened++
// ^ ^
// lo hi
// 5 3 *************
// ^ hi
// 7 4 *************
// ^ hi finish
// 6 1 *****
// 9 4 *********
Look at others solutions and steal the implementation
Time complexity: \(O(nlog(n))\), for sorting
Space complexity: \(O(n)\), to store the intervals
fun minTaps(n: Int, ranges: IntArray): Int {
var opened = 0
var lo = -1
var hi = 0
ranges.mapIndexed { i, v -> maxOf(0, i - v) to minOf(i + v, n) }
.filter { it.first != it.second }
.sortedBy { (from, _) -> from }
.onEach { (from, to) ->
if (from <= lo) hi = maxOf(hi, to)
else if (from <= hi) {
lo = hi
hi = to
opened++
}
if (hi == n) return opened
}
return -1
}
2366. Minimum Replacements to Sort the Array hard blog post substack
https://t.me/leetcode_daily_unstoppable/324
Minimum number of number splits to make an array non-decreasing
The first idea is, if we walk the array backwards, suffix is a maximum number. The second idea is how to split the current number optimally. Consider example:
// 3 8 3
// 3 53 3 +1 split
// 3 233 3 +1 split
// 12 233 3 +1 split
We shall not split 8
into numbers bigger than 3
, so keep extracting them, until some remainder reached.
However, this will not be the case for another example: 2 9 4
, when we split 9
-> 5 + 4
, we should not split 5
into 1 + 4
, but 2 + 3
, but optimal split is 3 + 3 + 3
, as 3 < 4
and 3 > 2
.
Another strategy is to consider how many split operations we should do: 9 / 4 = 2
, then we know the number of parts: 9 = (x split y split z) = 3 + 3 + 3
. Each part is guaranteed to be less than 4
but the maximum possible to sum up to 9
.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun minimumReplacement(nums: IntArray): Long {
if (nums.isEmpty()) return 0L
// 3 8 3
// 3 53 3 +1 split
// 3 233 3 +1 split
// 12 233 3 +1 split
var prev = nums.last()
var count = 0L
for (i in nums.lastIndex downTo 0) {
if (nums[i] == prev) continue
if (nums[i] < prev) prev = nums[i]
else if (prev == 1) count += nums[i] - 1
else if (nums[i] % prev == 0) count += (nums[i] / prev) - 1
else {
val splits = nums[i] / prev // 15 / 4 = 3
count += splits
val countParts = splits + 1 // 4 = (3 4 4 4)
prev = nums[i] / countParts // 15 / 4 = 3
}
}
return count
}
2483. Minimum Penalty for a Shop medium blog post substack
https://t.me/leetcode_daily_unstoppable/323
First index of minimum penalty in array, penalty ‘Y’-> 1, ‘N’ -> -1
Iterate from the end and compute the suffix penalty.
Suffix penalty is a difference between p_closed - p_opened
.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun bestClosingTime(customers: String): Int {
var p = 0
var iMin = customers.length
var pMin = 0
for (i in customers.lastIndex downTo 0) {
if (customers[i] == 'Y') p++ else p--
if (p <= pMin) {
iMin = i
pMin = p
}
}
return iMin
}
225. Implement Stack using Queues easy blog post substack
https://t.me/leetcode_daily_unstoppable/322
Create a Stack using Queue’s push/pop methods.
We can use a single Queue, and rotate it so that the newly inserted element will be on a first position:
1 push -> [1]
2 push -> [1 2] -> [2 1]
3 push -> [2 1 3] -> [1 3 2] -> [3 2 1]
Kotlin has no methods pop
, push
and peek
for ArrayDeque
, use removeFirst
, add
and first
.
Time complexity: \(O(n)\) for insertions, others are O(1)
Space complexity: \(O(n)\) for internal Queue, and O(1) operations overhead
class MyStack: Queue<Int> by LinkedList() {
fun push(x: Int) {
add(x)
repeat(size - 1) { add(pop()) }
}
fun pop() = remove()
fun top() = first()
fun empty() = isEmpty()
}
403. Frog Jump hard blog post substack
https://t.me/leetcode_daily_unstoppable/321
Can jump an array when each jump is k-1..k+1
of the previous
The simple Depth-First Search works: iterate over next array items and check if jump to them is in range k-1..k+1
. The result only depends on the array suffix and previous jump value k
, so can be safely cached. This will take n^3 operations in the worst case.
There is an improvement, we can use Binary Search to quickly find the range for the next positions. Time will be improved to n^2log(n).
In the interview, it is better to write Binary Search by yourself if you’re unsure about how to adapt built-in binarySearch
method to find bisectLeft
or bisectRight
borders.
if (-i - 1 in 0..lastIndex) -i - 1 else i
from in 0..to
, which also checks that from <= to
, from >= 0
and to >= 0
Time complexity: \(O(n^2log(n))\)
Space complexity: \(O(n^2)\)
fun canCross(stones: IntArray): Boolean {
val dp = mutableMapOf<Pair<Int, Int>, Boolean>()
fun dfs(i: Int, k: Int): Boolean = dp.getOrPut(i to k) {
if (i == stones.lastIndex) return@getOrPut true
var from = stones.binarySearch(stones[i] + maxOf(1, k - 1)).let {
if (-it - 1 in 0..stones.lastIndex) -it - 1 else it
}
var to = stones.binarySearch(stones[i] + k + 1).let {
if (-it - 2 in 0..stones.lastIndex) -it - 2 else it
}
return@getOrPut from in 0..to
&& (from..to).any { dfs(it, stones[it] - stones[i]) }
}
return dfs(0, 0)
}
646. Maximum Length of Pair Chain medium blog post substack
https://t.me/leetcode_daily_unstoppable/320
Max count non-overlaping intervals
The naive Dynamic Programming n^2 solution works, in a DFS choose between taking or skipping the pair, and cache by pos
and prev
.
Another solution, is just a line sweep algorithm: consider all ends
of the intervals in increasing order, skipping the overlapping ones. It will be optimal, as there are no overlapping intervals past the end
.
Sort and use the border
variable, that changes when from > border
.
Time complexity: \(O(nlog(n))\), for sorting
Space complexity: \(O(n)\), for the sorted array
fun findLongestChain(pairs: Array<IntArray>): Int {
var border = Int.MIN_VALUE
return pairs.sortedWith(compareBy({ it[1] }))
.count { (from, to) ->
(from > border).also { if (it) border = to }
}
}
97. Interleaving String medium blog post substack
https://t.me/leetcode_daily_unstoppable/319
Can a string be a merge of two other strings
Do DFS with two pointers, each time taking a char from the first or the second’s string, the third pointer will be p1 + p2
. The result will depend only on the remaining suffixes, so can be safely cached.
p1 + p2 * 100
Time complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\)
fun isInterleave(s1: String, s2: String, s3: String): Boolean {
val cache = mutableMapOf<Int, Boolean>()
fun dfs(p1: Int, p2: Int): Boolean = cache.getOrPut(p1 + p2 * 100) {
p1 < s1.length && p2 < s2.length && (
s1[p1] == s3[p1 + p2] && dfs(p1 + 1, p2)
|| s2[p2] == s3[p1 + p2] && dfs(p1, p2 + 1)
)
|| p1 == s1.length && s2.substring(p2) == s3.substring(p1 + p2)
|| p2 == s2.length && s1.substring(p1) == s3.substring(p1 + p2)
}
return s1.length + s2.length == s3.length && dfs(0, 0)
}
68. Text Justification hard blog post substack
https://t.me/leetcode_daily_unstoppable/318
Spread words
to lines, evenly spacing left->right, and left-spacing the last line
Scan word by word, checking maxWidth
overflow.
Separate word letters count and count of spaces.
To spread spaces left-evenly, iteratively add spaces one-by-one until maxWidth
reached.
Using Kotlin built-in functions helps to reduce boilerplate:
Time complexity: \(O(wn)\)
Space complexity: \(O(wn)\)
fun fullJustify(words: Array<String>, maxWidth: Int) = buildList<String> {
val line = mutableListOf<String>()
fun justifyLeft() = line.joinToString(" ").padEnd(maxWidth, ' ')
var wLen = 0
fun justifyFull() = buildString {
val sp = IntArray(line.size - 1) { 1 }
var i = 0
var len = wLen + line.size - 1
while (len++ < maxWidth && line.size > 1) sp[i++ % sp.size]++
line.forEachIndexed { i, w ->
append(w)
if (i < sp.size) append(" ".repeat(sp[i]))
}
}
words.forEachIndexed { i, w ->
if (wLen + line.size + w.length > maxWidth) {
add(if (line.size > 1) justifyFull() else justifyLeft())
line.clear()
wLen = 0
}
line += w
wLen += w.length
}
add(justifyLeft())
}
767. Reorganize String medium blog post substack
https://t.me/leetcode_daily_unstoppable/317
Create non repeated subsequent chars string from string
What will not work:
Now, use the hint.
If each time the most frequent char used greedily, solution magically works. (proving that is a homework)
Use Pri0rityQueue to store indices of the frequencies array. If the next char is repeated, and it is the only one left, we have no solution.
Time complexity: \(O(nlog(n))\), each poll and insert is log(n) in PQ
Space complexity: \(O(n)\), for the result
fun reorganizeString(s: String): String = buildString {
val freq = IntArray(128)
s.forEach { freq[it.toInt()]++ }
val pq = PriorityQueue<Int>(compareBy({ -freq[it] }))
for (i in 0..127) if (freq[i] > 0) pq.add(i)
while (pq.isNotEmpty()) {
var ind = pq.poll()
if (isNotEmpty() && get(0).toInt() == ind) {
if (pq.isEmpty()) return ""
ind = pq.poll().also { pq.add(ind) }
}
insert(0, ind.toChar())
if (--freq[ind] > 0) pq.add(ind)
}
}
168. Excel Sheet Column Title easy blog post substack
https://t.me/leetcode_daily_unstoppable/316
Excel col number to letter-number 1
-> A
, 28
-> AB
Just arithmetic conversion of number to string with radix of 26
instead of 10
. Remainder from division by 26 gives the last letter. Then the number must be divided by 26.
n-1
Time complexity: \(O(log(n))\), logarithm by radix of 26
Space complexity: \(O(log(n))\)
fun convertToTitle(columnNumber: Int): String = buildString {
var n = columnNumber
while (n > 0) {
insert(0, ((n - 1) % 26 + 'A'.toInt()).toChar())
n = (n - 1) / 26
}
}
459. Repeated Substring Pattern easy blog post substack
https://t.me/leetcode_daily_unstoppable/315
Consider example, abc abc abc
. Doing shift left 3
times we get the same string:
abcabcabc - original
bcabcabca - shift left by 1
cabcabcab - shift left by 1
abcabcabc - shift left by 1
Now, there is a technique called Rolling Hash: let’s calculate the hash like this: hash = x + 31 * hash
. After full string hash calculated, we start doing shifts:
// abcd
// a
// 32^0 * b + 32^1 * a
// 32^0 * c + 32^1 * b + 32^2 * a
// 32^0 * d + 32^1 * c + 32^2 * b + 32^3 * a
// bcda
// 32^0 * a + 32^1 * d + 32^2 * c + 32^3 * b = 32*(abcd-32^3a) +a=32abcd-(32^4-1)a
Observing this math equation, next rolling hash is shiftHash = 31 * shiftHash - 31^len + c
Time complexity: \(O(n)\), at most 2 full scans, and hashing gives O(1) time
Space complexity: \(O(1)\)
fun repeatedSubstringPattern(s: String): Boolean {
var hash = 0L
for (c in s) hash = c.toInt() + 31L * hash
var pow = 1L
repeat(s.length) { pow *= 31L }
pow--
var shiftHash = hash
return (0 until s.lastIndex).any { i ->
shiftHash = 31L * shiftHash - pow * s[i].toInt()
shiftHash == hash &&
s == s.substring(0, i + 1).let { it.repeat(s.length / it.length) }
}
}
1203. Sort Items by Groups Respecting Dependencies hard blog post substack
https://t.me/leetcode_daily_unstoppable/314
Sort items by groups and in groups given dependencies.
Use hint.
We can split items by groups and check groups dependencies. Next, do Topological Sort for groups and then do Topological Sort for items in each group.
Now, the tricks:
-1
as a separate group, code will become cleanerindegree == 0
Time complexity: \(O(nm + E)\)
Space complexity: \(O(n + n + E)\)
class G(count: Int, val fromTo: MutableMap<Int, MutableSet<Int>> = mutableMapOf()) {
operator fun get(k: Int) = fromTo.getOrPut(k) { mutableSetOf() }
val order: List<Int> by lazy {
val indegree = IntArray(count)
fromTo.values.onEach { it.onEach { indegree[it]++ } }
val queue = ArrayDeque<Int>(indegree.indices.filter { indegree[it] == 0 })
generateSequence { queue.poll() }
.onEach { fromTo[it]?.onEach { if (--indegree[it] == 0) queue += it } }
.toList().takeIf { it.size == count } ?: listOf()
}
}
fun sortItems(n: Int, m: Int, group: IntArray, beforeItems: List<List<Int>>): IntArray {
var groupsCount = m
for (i in 0 until n) if (group[i] == -1) group[i] = groupsCount++
val items = G(n)
val groups = G(groupsCount)
for (to in beforeItems.indices)
for (from in beforeItems[to])
if (group[to] == group[from]) items[from] += to
else groups[group[from]] += group[to]
return groups.order.flatMap { g -> items.order.filter { group[it] == g } }.toIntArray()
}
1489. Find Critical and Pseudo-Critical Edges in Minimum Spanning Tree hard blog post substack
https://t.me/leetcode_daily_unstoppable/313
List of list of must-have
edges and list of optional
edges for Minimum Weight Minimum Spanning Tree
Use hints.
Minimum Spanning Tree can be obtained by sorting edges and adding not connected one-by-one using Union-Find
After we found target
minimum weight, we can check how each node contributes: if removing the node increases the target
, the node is a must-have
. Also, if force using node in a spanning tree doesn’t change the target
, node is optional
.
Time complexity:
\(O(E^2 + EV)\), sorting edges takes ElogE
, then cycle E
times algorithm of E+V
Space complexity:
\(O(E + V)\), E
for sorted edges, V
for Union-Find array
fun findCriticalAndPseudoCriticalEdges(n: Int, edges: Array<IntArray>): List<List<Int>> {
val sorted = edges.indices.sortedWith(compareBy({ edges[it][2] }))
fun minSpanTreeW(included: Int = -1, excluded: Int = -1): Int {
val uf = IntArray(n) { it }
fun find(x: Int): Int = if (x == uf[x]) x else find(uf[x]).also { uf[x] = it }
fun union(ind: Int): Int {
val (a, b, w) = edges[ind]
return if (find(a) == find(b)) 0 else w.also { uf[find(b)] = find(a) }
}
return ((if (included < 0) 0 else union(included)) + sorted
.filter { it != excluded }.map { union(it) }.sum()!!)
.takeIf { (0 until n).all { find(0) == find(it) } } ?: Int.MAX_VALUE
}
val target = minSpanTreeW()
val critical = mutableListOf<Int>()
val pseudo = mutableListOf<Int>()
edges.indices.forEach {
if (minSpanTreeW(excluded = it) > target) critical += it
else if (minSpanTreeW(included = it) == target) pseudo += it
}
return listOf(critical, pseudo)
}
1615. Maximal Network Rank medium blog post substack
https://t.me/leetcode_daily_unstoppable/312
Max edges count for each pair of nodes
We can just count edges for each node, then search for max in an n^2 for-loop.
HashSet
to check contains
in O(1)Time complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\), there are up to n^2 edges
fun maximalNetworkRank(n: Int, roads: Array<IntArray>): Int {
val fromTo = mutableMapOf<Int, HashSet<Int>>()
roads.forEach { (from, to) ->
fromTo.getOrPut(from) { HashSet() } += to
fromTo.getOrPut(to) { HashSet() } += from
}
var max = 0
for (a in 0 until n) {
for (b in a + 1 until n) {
val countA = fromTo[a]?.size ?: 0
val countB = fromTo[b]?.size ?: 0
val direct = fromTo[a]?.contains(b) ?: false
max = maxOf(max, countA + countB - (if (direct) 1 else 0))
}
}
return max
}
542. 01 Matrix medium blog post substack
https://t.me/leetcode_daily_unstoppable/311
Distances to 0
in an 0-1
matrix
Depth-First search will not work, as the path to 0
must radiate to all directions.
We can start a Breadth-First Search waves from each 0
. Each BFS step increases distance by 1.
dir
array for a simpler codeTime complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun updateMatrix(mat: Array<IntArray>): Array<IntArray> {
val res = Array(mat.size) { IntArray(mat[0].size) { -1 } }
val dir = listOf(-1 to 0, 0 to 1, 1 to 0, 0 to -1)
with(ArrayDeque<Pair<Int, Int>>()) {
for (y in 0..mat.lastIndex)
for (x in 0..mat[0].lastIndex)
if (mat[y][x] == 0) add(y to x)
var dist = 0
while (isNotEmpty()) {
repeat(size) {
val (y, x) = poll()
if (res[y][x] == -1) {
res[y][x] = dist
for ((dx, dy) in dir) {
val y1 = y + dy
val x1 = x + dx
if (y1 in 0..mat.lastIndex && x1 in 0..mat[0].lastIndex)
add(y1 to x1)
}
}
}
dist++
}
}
return res
}
239. Sliding Window Maximum medium blog post substack
https://t.me/leetcode_daily_unstoppable/310
List of sliding window’s maximums
To quickly find a maximum in a sliding window, consider example:
Input: nums = [1,3,-1,-3,5,3,6,7], k = 3
Window position Max
--------------- -----
[# 3 -1] _ _ _ _ _ 3
_ [3 -1 -3] _ _ _ _ 3
_ _ [ # # 5] _ _ _ 5
_ _ _ [ # 5 3] _ _ 5
_ _ _ _ [# # 6] _ 6
_ _ _ _ _ [# # 7] 7
After each new maximum appends to the end of the window, they become the maximum until the window slides it out, so all lesser positions to the left of it are irrelevant.
We can use a decreasing Stack
technique to remove all the smaller elements. However, to maintain a window size, we’ll need a Queue
.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun maxSlidingWindow(nums: IntArray, k: Int): IntArray = with(ArrayDeque<Int>()) {
val res = mutableListOf<Int>()
nums.forEachIndexed { i, x ->
while (isNotEmpty() && nums[peekLast()] < x) removeLast()
add(i)
while (isNotEmpty() && i - peekFirst() + 1 > k) removeFirst()
if (i >= k - 1) res += nums[peekFirst()]
}
return res.toIntArray()
}
86. Partition List medium blog post substack
https://t.me/leetcode_daily_unstoppable/309
Partition a Linked List by x
value
Keep two nodes for less
and for more
than x, and add to them, iterating over the list. Finally, concatenate more
to less
.
next
to null
dummy head
techniqueTime complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun partition(head: ListNode?, x: Int): ListNode? {
val dummyLess = ListNode(0)
val dummyMore = ListNode(0)
var curr = head
var less = dummyLess
var more = dummyMore
while (curr != null) {
if (curr.`val` < x) {
less.next = curr
less = curr
} else {
more.next = curr
more = curr
}
val next = curr.next
curr.next = null
curr = next
}
less.next = dummyMore.next
return dummyLess.next
}
215. Kth Largest Element in an Array medium blog post substack
https://t.me/leetcode_daily_unstoppable/308
Kth largest in an array
There is a known Quckselect algorithm:
pivot
pivot
is less than target
, repeat on the left sidepivot
To do a partition:
buffer
on the leftpivot
value which to compare all the elementsnums[i] < pivot
, put and grow the bufferFor divide-and-conquer loop:
from == to
from = pi + 1
, to = pi - 1
Time complexity: \(O(n) -> O(n^2)\), the worst case is n^2
Space complexity: \((O(1))\), but array is modified
fun findKthLargest(nums: IntArray, k: Int): Int {
var from = 0
var to = nums.lastIndex
fun swap(a: Int, b: Int) { nums[a] = nums[b].also { nums[b] = nums[a] } }
val target = nums.size - k
while (from <= to) {
var pi = from
var pivot = nums[to]
for (i in from until to) if (nums[i] < pivot) swap(i, pi++)
swap(to, pi)
if (pi == target) return nums[pi]
if (pi < target) from = pi + 1
if (pi > target) to = pi - 1
}
return -1
}
2369. Check if There is a Valid Partition For The Array medium blog post substack
https://t.me/leetcode_daily_unstoppable/307
Is it possible to partition an array of 2
or 3
equal nums or 3
increasing nums.
Hint: don’t spend much time trying to write a greedy solution.
We can consider every suffix of an array and make it a subproblem. Given it depends on only of the starting position, it can be safely cached.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun validPartition(nums: IntArray): Boolean {
val cache = mutableMapOf<Int, Boolean>()
fun dfs(pos: Int): Boolean = cache.getOrPut(pos) {
if (pos == nums.size) true
else if (pos + 1 > nums.lastIndex) false
else {
val diff1 = nums[pos + 1] - nums[pos]
if (diff1 == 0 && dfs(pos + 2)) true
else if (pos + 2 > nums.lastIndex) false
else {
val diff2 = nums[pos + 2] - nums[pos + 1]
(diff1 == 0 && diff2 == 0 || diff1 == 1 && diff2 == 1) && dfs(pos + 3)
}
}
}
return dfs(0)
}
63. Unique Paths II medium blog post substack
https://t.me/leetcode_daily_unstoppable/306
Number of right-down ways tl->br in a matrix with obstacles
Each time the robot moves in one direction gives a separate path. If two directions are possible, the number of paths gets added.
For example,
r r # 0
r 2r 2r 2r
0 # 2r 4r
On the first row, the single path goes up to 1
.
On the second row, direct path down added to direct path right.
On the third row, the same happens when top and left numbers of paths are not 0.
Use a separate row
array to remember previous row paths counts.
Time complexity: \(O(nm)\)
Space complexity: \(O(nm)\)
fun uniquePathsWithObstacles(obstacleGrid: Array<IntArray>): Int {
val row = IntArray(obstacleGrid[0].size)
row[0] = 1
for (r in obstacleGrid)
for (x in r.indices)
if (r[x] != 0) row[x] = 0
else if (x > 0) row[x] += row[x - 1]
return row.last()
}
In Emojia's forgotten 🌌 corner, where time doesn't merely flow—it waltzes 💃,
spinning tales of lost yesterdays 🕰️ and unborn tomorrows ⌛, stands the
whispered legend of the Time Labyrinth. Not merely walls and corridors, but
a tapestry of fate's myriad choices, echoing distant memories and futures yet
conceived.
Bolt, the lonely automaton 🤖, not born but dreamt into existence by starlight ✨
and cosmic whimsy, felt an inexplicable yearning towards the 🏁 - the Time Nexus.
Ancient breezes 🍃 carried murmurs, not of it being an end, but a kaleidoscope
🎨 gateway to every pulse and flutter ❤️ of chronology's capricious dance 🌊.
╔═══╤═══╤═══╤═══╗
║🤖 │ 0 │🚫 │ 0 ║
╟───┼───┼───┼───╢
║ 0 │ 0 │ 0 │ 0 ║
╟───┼───┼───┼───╢
║ 0 │🚫 │ 0 │🏁 ║
╚═══╧═══╧═══╧═══╝
With each step, the fabric of reality quivered. Shadows of histories 🎶,
cosmic echoes 🌍, diverged and converged, painting and erasing moments of
what was, is, and could be.
---
Standing before the 🚫, it wasn't a barrier for Bolt, but a silent riddle:
"What song of the cosmos 🎵 shall you hum today, wanderer?"
╔═══╤═══╤═══╤═══╗
║🤖 │ ➡️ │🚫 │ 0 ║
╟───┼───┼───┼───╢
║ 0 │ 0 │ 0 │ 0 ║
╟───┼───┼───┼───╢
║ 0 │ 🚫 │ 0 │🏁 ║
╚═══╧═══╧═══╧═══╝
Dreamlike avenues 🛤️ unfurled, painting multitudes of futures in the vivid
colors of a universe in spring. In this chronal dance, Bolt secretly hoped
to outrace its own echoes, to be the first at the Nexus.
---
Junctions whispered with the delicate hum 🎵 of countless Bolts, each a tale,
a fate, a fleeting note in the grand cosmic symphony.
╔═══╤═══╤═══╤═══╗
║🤖 │ ➡️ │🚫 │ 0 ║
╟───┼───┼───┼───╢
║⬇️ │ 2➡️│2➡️│2➡️║
╟───┼───┼───┼───╢
║ 0 │ 🚫 │ 0 │🏁 ║
╚═══╧═══╧═══╧═══╝
Yet, as the Time Nexus loomed, revealing its vast enigma, a sense of profound
disquiet engulfed Bolt. Not only had another reflection reached before, but a
sea of mirrored selves stared back.
╔═══╤═══╤═══╤═══╗
║🤖 │ ➡️ │🚫 │ 0 ║
╟───┼───┼───┼───╢
║⬇️ │ 2➡️│2➡️│2➡️║
╟───┼───┼───┼───╢
║⬇️ │ 🚫 │2⬇️│4🏁║
╚═══╧═══╧═══╧═══╝
In that echoing vastness, Bolt's singular hope was smothered. In the dance of
time, amidst countless reflections, it whispered a silent, desperate question:
Which tune, which cadence, which moment 🎶 was truly its own in this timeless
waltz?
518. Coin Change II medium blog post substack
https://t.me/leetcode_daily_unstoppable/305
Ways to make amount
with array of coins
This is a classical Dynamic Programming problem: the result is only depending on inputs – coins
subarray and the amount
, so can be cached.
In a Depth-First search manner, consider possibilities of taking
a coin and skipping
to the next.
Time complexity: \(O(nm)\)
Space complexity: \(O(nm)\)
fun change(amount: Int, coins: IntArray): Int {
val cache = Array(coins.size) { IntArray(amount + 1) { -1 } }
fun dfs(curr: Int, left: Int): Int = if (left == 0) 1
else if (left < 0 || curr == coins.size) 0
else cache[curr][left].takeIf { it >= 0 } ?: {
dfs(curr, left - coins[curr]) + dfs(curr + 1, left)
}().also { cache[curr][left] = it }
return dfs(0, amount)
}
81. Search in Rotated Sorted Array II medium blog post substack
https://t.me/leetcode_daily_unstoppable/304
Binary Search in a rotated array with duplicates
There are several cases:
For more robust code:
lo
and hi
lo == hi
nums[mid] == target
lo = mid + 1
, hi = mid - 1
<
& >
are simpler to reason about than inclusive <=
, =>
Time complexity: \(O(n)\), the worst case is linear in a long array of duplicates
Space complexity: \(O(1)\)
fun search(nums: IntArray, target: Int): Boolean {
var lo = 0
var hi = nums.lastIndex
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (nums[mid] == target) return true
if (nums[lo] < nums[hi]) { // normal case
if (nums[mid] < target) lo = mid + 1 else hi = mid - 1
} else if (nums[lo] > nums[hi]) { // pivot case
if (nums[mid] > nums[hi]) {
// pivot on the right
// 5 6 7 8 9 1 2
// t m p
if (target in nums[lo]..nums[mid]) hi = mid - 1 else lo = mid + 1
} else {
// pivot on the left
// 9 1 2 3 4
// p m t
if (target in nums[mid]..nums[hi]) lo = mid + 1 else hi = mid - 1
}
} else hi-- // nums[lo] == nums[hi]
}
return false
}
2616. Minimize the Maximum Difference of Pairs medium blog post substack
https://t.me/leetcode_daily_unstoppable/303
Minimum of maximums possible p
diffs of distinct array positions
The hint
is misleading, given the problem size 10^5 DP approach will give TLE, as it is n^2.
The real hint is:
diff
, how many pairs there are in an array, where pair_diff <= diff
?diff
will that number grow or shrink?Using this hint, we can solve the problem with Binary Search, as with growth of diff
, there is a flip of when we can take p
numbers and when we can’t.
When counting the diffs, we use Greedy
approach, and take the first possible, skipping its sibling. This will work, because we’re answering the questions of how many
rather than maximum/minimum
.
For more robust Binary Search, use:
lo
, hi
lo == hi
if (count >= p) res = minOf(res, mid)
lo = mid + 1
, hi = mid - 1
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(1)\)
fun minimizeMax(nums: IntArray, p: Int): Int {
nums.sort()
var lo = 0
var hi = nums.last() - nums.first()
var res = hi
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
var i = 1
var count = 0
while (i < nums.size) if (nums[i] - nums[i - 1] <= mid) {
i += 2
count++
} else i++
if (count >= p) res = minOf(res, mid)
if (count >= p) hi = mid - 1 else lo = mid + 1
}
return res
}
33. Search in Rotated Sorted Array medium blog post substack
https://t.me/leetcode_daily_unstoppable/302
Binary Search in a shifted array
The special case is when lo
> hi
, otherwise it is a Binary Search.
Then there are two cases:
lo < mid
- monotonic part is on the leftlo >= mid
- monotonic part is on the rightCheck the monotonic part immediately, otherwise go to the other part.
For more robust code:
lo
and hi
target == nums[mid]
lo = mid + 1
, hi = mid - 1
lo == hi
Time complexity: \(O(log(n))\)
Space complexity: \(O(log(n))\)
fun search(nums: IntArray, target: Int): Int {
var lo = 0
var hi = nums.lastIndex
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (target == nums[mid]) return mid
if (nums[lo] > nums[hi]) {
if (nums[lo] > nums[mid]) {
if (target < nums[mid] || target > nums[hi]) hi = mid - 1 else lo = mid + 1
} else {
if (target > nums[mid] || target < nums[lo]) lo = mid + 1 else hi = mid - 1
}
} else if (target < nums[mid]) hi = mid - 1 else lo = mid + 1
}
return -1
}
74. Search a 2D Matrix medium blog post substack
https://t.me/leetcode_daily_unstoppable/301
2D Binary Search
Just a Binary Search
For more robust code:
lo
and hi
lo == hi
lo = mid + 1
, hi = mid - 1
Time complexity: \(O(log(n*m))\)
Space complexity: \(O(1)\)
fun searchMatrix(matrix: Array<IntArray>, target: Int): Boolean {
var lo = 0
var hi = matrix.lastIndex
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
val row = matrix[mid]
if (target in row.first()..row.last())
return row.binarySearch(target) >= 0
if (target < row.first()) hi = mid - 1 else lo = mid + 1
}
return false
}
920. Number of Music Playlists hard blog post substack
https://t.me/leetcode_daily_unstoppable/300
Playlists number playing n
songs goal
times, repeating each once in a k
times
We can search through the problem space, taking each new song with the given rules: song can be repeated only after another k
song got played. When we have the goal
songs, check if all distinct songs are played.
We can cache the solution by curr
and used
map, but that will give TLE.
The hard trick here is that the result only depends on how many distinct songs are played.
Use DFS and memo.
Time complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\)
fun numMusicPlaylists(n: Int, goal: Int, k: Int): Int {
val cache = mutableMapOf<Pair<Int, Int>, Long>()
fun dfs(curr: Int, used: Map<Int, Int>): Long = cache.getOrPut(curr to used.size) {
if (curr > goal) {
if ((1..n).all { used.contains(it) }) 1L else 0L
} else (1..n).asSequence().map { i ->
if (curr <= used[i] ?: 0) 0L else
dfs(curr + 1, used.toMutableMap().apply { this[i] = curr + k })
}.sum()!! % 1_000_000_007L
}
return dfs(1, mapOf()).toInt()
}
95. Unique Binary Search Trees II medium blog post substack
https://t.me/leetcode_daily_unstoppable/299
All possible Binary Search Trees for 1..n numbers
One way to build all possible BST is to insert numbers in all possible ways. We can do this with a simple backtracking, given the small n <= 8
. To remove duplicates, we can print the tree and use it as a hash key.
\(O(n!* nlog(n))\), as the recursion depth is n, each time iterations go as n * (n - 1) * (n - 2) * … * 2 * 1, which is equal to n!. The final step of inserting elements is nlog(n), and building a hash is n, which is < nlogn, so not relevant.
\(O(n!)\), is a number of permutations
fun insert(x: Int, t: TreeNode?): TreeNode = t?.apply {
if (x > `val`) right = insert(x, right)
else left = insert(x, left)
} ?: TreeNode(x)
fun print(t: TreeNode): String =
"[${t.`val`} ${t.left?.let { print(it) }} ${t.right?.let { print(it) }}]"
fun generateTrees(n: Int): List<TreeNode?> {
val stack = Stack<Int>()
val lists = mutableListOf<TreeNode>()
fun dfs(m: Int): Unit = if (m == 0)
lists += TreeNode(stack[0]).apply { for (i in 1 until n) insert(stack[i], this) }
else for (i in 0 until n) if (m and (1 shl i) != 0) {
stack.push(i + 1)
dfs(m xor (1 shl i))
stack.pop()
}
dfs((1 shl n) - 1)
return lists.distinctBy { print(it) }
}
Another divide-and-conquer solution, that I didn’t think of Another divide-and-conquer solution, that I didn’t think of
139. Word Break medium blog post substack
https://t.me/leetcode_daily_unstoppable/298
If a word
is a wordDict
concatenation
To quickly find out if a sequence, we can use Trie
. Then, we can search with DFS any possible split. As the result only depends on the argument, we can safely cache it.
Write a Trie
and DFS, no tricks here.
Time complexity:
\(O(wn)\), w—is words count in s
Space complexity: \(O(w + 26^l)\), l—is the longest word in a dict
class Trie(var isWord: Boolean = false) { val next = mutableMapOf<Char, Trie>() }
fun wordBreak(s: String, wordDict: List<String>): Boolean {
val root = Trie()
wordDict.forEach {
var t = root
it.forEach { t = t.next.getOrPut(it) { Trie() } }
t.isWord = true
}
val cache = mutableMapOf<Int, Boolean>()
fun dfs(pos: Int): Boolean = pos == s.length || cache.getOrPut(pos) {
var t: Trie? = root
s.withIndex().asSequence().drop(pos).takeWhile { t != null }
.any { (i, c) ->
t = t?.next?.get(c)
t?.isWord == true && dfs(i + 1)
}
}
return dfs(0)
}
17. Letter Combinations of a Phone Number medium blog post substack
https://t.me/leetcode_daily_unstoppable/297
Possible words from phone keyboard
Just a naive DFS and Backtraking will solve the problem, as the number is short
n
, each time we iterate over ‘3’ or ‘4’ letters, for example:12 ->
abc def
a d
a e
a f
b d
b e
b f
c d
c e
c f
Each new number multiply previous count by 3
or 4
. The final joinToString
gives another n
multiplier.
fun letterCombinations(digits: String): List<String> = mutableListOf<String>().apply {
val abc = arrayOf("abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz")
val list = Stack<Char>()
fun dfs(pos: Int) {
if (list.size == digits.length) {
if (list.isNotEmpty()) add(list.joinToString(""))
} else abc[digits[pos].toInt() - '2'.toInt()].forEach {
list.push(it)
dfs(pos + 1)
list.pop()
}
}
dfs(0)
}
46. Permutations medium blog post substack
https://t.me/leetcode_daily_unstoppable/296
List of all numbers permutations
As the total count of number is small, we can just brute force the solution. We can use DFS and a backtracking technique: add number to the list pre-order then remove it post-order.
Iterate over all numbers and choose every number not in a bit mask
Time complexity:
\(O(n * n!)\), as we go n * (n - 1) * (n - 2) * .. * 2 * 1
Space complexity: \((n!)\)
fun permute(nums: IntArray): List<List<Int>> = mutableListOf<List<Int>>().apply {
val list = mutableListOf<Int>()
fun dfs(mask: Int): Unit = if (list.size == nums.size) this += list.toList()
else nums.forEachIndexed { i, n ->
if (mask and (1 shl i) == 0) {
list += n
dfs(mask or (1 shl i))
list.removeAt(list.lastIndex)
}
}
dfs(0)
}
77. Combinations medium blog post substack
https://t.me/leetcode_daily_unstoppable/295
All combinations choosing k
numbers from 1..n
numbers
As total number is 20
, we can use bit mask to generate all possible 2^n
bit masks, then choose only k
1
-bits masks and generate lists.
Let’s write a Kotlin one-liner
Time complexity: \(O(n2^n)\)
Space complexity: \(O(n2^n)\)
fun combine(n: Int, k: Int): List<List<Int>> = (0 until (1 shl n))
.filter { Integer.bitCount(it) == k }
.map { mask -> (1..n).filter { mask and (1 shl it - 1) != 0 } }
712. Minimum ASCII Delete Sum for Two Strings medium blog post substack
https://t.me/leetcode_daily_unstoppable/292
Minimum removed chars sum to make strings equal
This is a known Dynamic Programming problem about the minimum edit distance. We can walk both strings and at each time choose what char to take and what to skip. The result is dependent only from the arguments, so can be cached.
Let’s use DFS and memo.
Time complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\)
fun minimumDeleteSum(s1: String, s2: String): Int {
val cache = mutableMapOf<Pair<Int, Int>, Int>()
fun dfs(p1: Int, p2: Int): Int = cache.getOrPut(p1 to p2) { when {
p1 == s1.length && p2 == s2.length -> 0
p1 == s1.length -> s2.drop(p2).map { it.toInt() }.sum()!!
p2 == s2.length -> s1.drop(p1).map { it.toInt() }.sum()!!
s1[p1] == s2[p2] -> dfs(p1 + 1, p2 + 1)
else -> minOf(s1[p1].toInt() + dfs(p1 + 1, p2), s2[p2].toInt() + dfs(p1, p2 + 1))
} }
return dfs(0, 0)
}
664. Strange Printer hard blog post substack
https://t.me/leetcode_daily_unstoppable/291
Minimum continuous overrides by the same character to make a string
The main idea comes to mind when you consider some palindromes
as example:
abcccba
When we consider the next character ccc + b
, we know, that the optimal number of repaints is Nc + 1
. Or, bccc + b
, the optimal is 1 + Nc
.
However, the Dynamic Programming formula for finding a palindrome didn’t solve this case: ababa
, as clearly, the middle a
can be written in a single path aaaaa
.
Another idea, is to split the string: ab + aba
. Number for ab
= 2, and for aba
= 2. But, as first == last, we paint a
only one time, so dp[from][to] = dp[from][a] + dp[a + 1][to]
.
As we didn’t know if our split is the optimal one, we must consider all of them.
Time complexity: \(O(n^3)\)
Space complexity: \(O(n^2)\)
fun strangePrinter(s: String): Int = with(Array(s.length) { IntArray(s.length) }) {
s.mapIndexed { to, sto ->
(to downTo 0).map { from -> when {
to - from <= 1 -> if (s[from] == sto) 1 else 2
s[from] == sto -> this[from + 1][to]
else -> (from until to).map { this[from][it] + this[it + 1][to] }.min()!!
}.also { this[from][to] = it }
}.last()!!
}.last()!!
}
808. Soup Servings medium blog post substack
https://t.me/leetcode_daily_unstoppable/290
Probability of soup A
drained first or both A and B
with 0.5
multiplier.
The formula in the examples gives us the correct way to calculate the probabilities: each time we make a choice with probability of 1/4
. After we arrive with the final condition, we use multipliers 1.0
for A win, 0.5
for both A and B and 0.0
for B win.
This is a simple DFS + cache dynamic programming problem.
However, this give TLE or OOM, as the N
is too big.
At that point, the interview is over, and you safely can go home and see the answers in leetcode.com website.
To solve TLE & OOM, we must observe all the possible answers:
val ans = doubleArrayOf(
0.50000, 0.62500, 0.62500, 0.65625, 0.71875, 0.74219, 0.75781, 0.78516, 0.79688, 0.81787,
0.82764, 0.84485, 0.85217, 0.86670, 0.87256, 0.88483, 0.88963, 0.90008, 0.90406, 0.91301,
0.91634, 0.92405, 0.92687, 0.93353, 0.93593, 0.94170, 0.94376, 0.94878, 0.95056, 0.95493,
0.95646, 0.96029, 0.96162, 0.96497, 0.96612, 0.96906, 0.97007, 0.97265, 0.97353, 0.97580,
0.97657, 0.97857, 0.97924, 0.98100, 0.98160, 0.98315, 0.98367, 0.98505, 0.98551, 0.98672,
0.98713, 0.98820, 0.98856, 0.98951, 0.98983, 0.99067, 0.99095, 0.99170, 0.99195, 0.99261,
0.99283, 0.99342, 0.99362, 0.99414, 0.99431, 0.99478, 0.99493, 0.99535, 0.99548, 0.99585,
0.99597, 0.99630, 0.99640, 0.99670, 0.99679, 0.99705, 0.99714, 0.99737, 0.99744, 0.99765,
0.99772, 0.99790, 0.99796, 0.99812, 0.99818, 0.99832, 0.99837, 0.99850, 0.99854, 0.99866,
0.99870, 0.99880, 0.99884, 0.99893, 0.99896, 0.99904, 0.99907, 0.99914, 0.99917, 0.99923,
0.99925, 0.99931, 0.99933, 0.99939, 0.99940, 0.99945, 0.99947, 0.99951, 0.99952, 0.99956,
0.99957, 0.99961, 0.99962, 0.99965, 0.99966, 0.99968, 0.99969, 0.99972, 0.99972, 0.99975,
0.99975, 0.99977, 0.99978, 0.99980, 0.99980, 0.99982, 0.99982, 0.99984, 0.99984, 0.99985,
0.99986, 0.99987, 0.99987, 0.99988, 0.99989, 0.99989, 0.99990, 0.99991, 0.99991, 0.99991,
0.99992, 0.99992, 0.99993, 0.99993, 0.99993, 0.99994, 0.99994, 0.99994, 0.99995, 0.99995,
0.99995, 0.99996, 0.99996, 0.99996, 0.99996, 0.99996, 0.99997, 0.99997, 0.99997, 0.99997,
0.99997, 0.99997, 0.99997, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998,
0.99998, 0.99998, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999,
0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999,
0.99999, 0.99999, 0.99999, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000
)
Basically, after a certain point, there is no new kind of answer.
As to solve this problem we must observe all the answers, a lookup table as a valid choice for the solution.
Time complexity: \(O(1)\)
Space complexity: \(O(1)\)
val ans = doubleArrayOf(
0.50000, 0.62500, 0.62500, 0.65625, 0.71875, 0.74219, 0.75781, 0.78516, 0.79688, 0.81787,
0.82764, 0.84485, 0.85217, 0.86670, 0.87256, 0.88483, 0.88963, 0.90008, 0.90406, 0.91301,
0.91634, 0.92405, 0.92687, 0.93353, 0.93593, 0.94170, 0.94376, 0.94878, 0.95056, 0.95493,
0.95646, 0.96029, 0.96162, 0.96497, 0.96612, 0.96906, 0.97007, 0.97265, 0.97353, 0.97580,
0.97657, 0.97857, 0.97924, 0.98100, 0.98160, 0.98315, 0.98367, 0.98505, 0.98551, 0.98672,
0.98713, 0.98820, 0.98856, 0.98951, 0.98983, 0.99067, 0.99095, 0.99170, 0.99195, 0.99261,
0.99283, 0.99342, 0.99362, 0.99414, 0.99431, 0.99478, 0.99493, 0.99535, 0.99548, 0.99585,
0.99597, 0.99630, 0.99640, 0.99670, 0.99679, 0.99705, 0.99714, 0.99737, 0.99744, 0.99765,
0.99772, 0.99790, 0.99796, 0.99812, 0.99818, 0.99832, 0.99837, 0.99850, 0.99854, 0.99866,
0.99870, 0.99880, 0.99884, 0.99893, 0.99896, 0.99904, 0.99907, 0.99914, 0.99917, 0.99923,
0.99925, 0.99931, 0.99933, 0.99939, 0.99940, 0.99945, 0.99947, 0.99951, 0.99952, 0.99956,
0.99957, 0.99961, 0.99962, 0.99965, 0.99966, 0.99968, 0.99969, 0.99972, 0.99972, 0.99975,
0.99975, 0.99977, 0.99978, 0.99980, 0.99980, 0.99982, 0.99982, 0.99984, 0.99984, 0.99985,
0.99986, 0.99987, 0.99987, 0.99988, 0.99989, 0.99989, 0.99990, 0.99991, 0.99991, 0.99991,
0.99992, 0.99992, 0.99993, 0.99993, 0.99993, 0.99994, 0.99994, 0.99994, 0.99995, 0.99995,
0.99995, 0.99996, 0.99996, 0.99996, 0.99996, 0.99996, 0.99997, 0.99997, 0.99997, 0.99997,
0.99997, 0.99997, 0.99997, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998,
0.99998, 0.99998, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999,
0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999, 0.99999,
0.99999, 0.99999, 0.99999, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000, 1.00000
)
fun soupServings(n: Int): Double = if (n >= 199 * 25) 1.0 else ans[Math.ceil(n / 25.0).toInt()]
/*
if (n > 4000) return 1.0
val cache = mutableMapOf<Pair<Int, Int>, Double>()
fun dfs(a: Int, b: Int): Double = cache.getOrPut(a to b) {
if (a <= 0 && b <= 0) return 0.5
if (a > 0 && b <= 0) return 0.0
if (a <= 0 && b > 0) return 1.0
(dfs(a - 100, b) +
dfs(a - 75, b - 25) +
dfs(a - 50, b - 50) +
dfs(a - 25, b - 75)) * 0.25
}
return dfs(n, n)
*/
486. Predict the Winner medium blog post substack
https://t.me/leetcode_daily_unstoppable/289
Optimally taking numbers from an array's ends
can one player win another
The optimal strategy for the current player will be to search the maximum score of total sum - optimal another
. The result can be cached as it only depends on the input array.
Write the DFS and cache by lo
and hi
.
Long
to avoid overflowTime complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\)
fun PredictTheWinner(nums: IntArray): Boolean {
val cache = Array(nums.size) { LongArray(nums.size) { -1L } }
fun dfs(lo: Int, hi: Int, currSum: Long): Long = cache[lo][hi].takeIf { it >= 0 } ?: {
if (lo == hi) nums[lo].toLong()
else if (lo > hi) 0L
else currSum - minOf(
dfs(lo + 1, hi, currSum - nums[lo]),
dfs(lo, hi - 1, currSum - nums[hi])
)
}().also { cache[lo][hi] = it }
val sum = nums.asSequence().map { it.toLong() }.sum()!!
return dfs(0, nums.lastIndex, sum).let { it >= sum - it }
}
2141. Maximum Running Time of N Computers hard blog post substack
https://t.me/leetcode_daily_unstoppable/288
Maximum time to use n
batteries in parallel
Batteries 5 5 5
is equal to 1 2 3 4 5
to run 3
computers for 5
minutes.
Batteries are swapped instantly, so we can drain all 1 2 3 4 5
with just 3
computers, but if a pack is 1 2 3 4 100
we can only drain 5
from the last 100
battery. (or less)
Energy of 5 5 5
is 15
to run for 5
minutes.
Energy in 1 2 3 4 100
is 1+2+3+4+5
when run for 5
minutes.
Energy in 1 2 3 4 100
is 1+2+3+4+4
when run for 4
minutes.
Energy in 1 2 3 4 100
is 1+2+3+3+3
when run for 3
minutes.
The Binary Search idea is first to mind, as with growth of run time the function of canRun
do the flip.
However, to detect if we canRun
the given time
is not so trivial.
We can use all batteries by swapping them every minute. To use 5
batteries in 3
computers, we can first use the max capacity and change others:
1 2 3 4 5
1 1 1
1 1 1
1 1 1
1 1 1
1 1 1
In this example, time = 5
. Or we can have just 3
batteries with capacity of 5
each: 5 5 5
. What if we add another battery:
1 2 3 4 5 9
1 1 1
1 1 1
1 1 1
1 1 1
1 1 1
1 1 1
1 1 1
Time
becomes 7
, or we can have 7 7 7
battery pack with total energy = 3 * 7 = 21
. And we don’t use 1
yet.
Let’s observe the energy for the time = 7
:
1 2 3 4 5 9
* 1 1 1 1 1
1 1 1 1 1
1 1 1 1
1 1 1
1 1
1
1
We didn’t use 1
, but had we another 1
the total energy will be 21 + 1 + 1 + 1(from 9)
or 24
, which is equal to 3 * 8
, or time = 8
.
So, by this diagram, we can take at most time
power units from each battery.
So, our function canRun(time)
is: energy(time) >= time * n
. Energy is a sum of all batteries running at most time
.
Binary Search:
lo
& hi
lo == hi
res = mid
lo = mid + 1
, hi = mid - 1
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(1)\)
fun maxRunTime(n: Int, batteries: IntArray): Long {
// n=3 1 2 3 4 5 6 7 9
// time = 4
// we need 4 4 4, take 1 2 3 4 4 4 4 4
// time = 5
// we need 5 5 5, take 1 2 3 4 5 5 5 5
// n=3 3 3 3 80
// time = 1 1 1 1 1 vs 1 1 1
// time = 2 2 2 2 2 vs 2 2 2
// time = 3 3 3 3 3 vs 3 3 3
// time = 4 3 3 3 4 (13) vs 4 4 4 (16)
// time = 5 3 3 3 5 (14) vs 5 5 5 (15)
// time = 6 3 3 3 6 (15) vs 6 6 6 (18)
var lo = 0L
var hi = batteries.asSequence().map { it.toLong() }.sum() ?: 0L
var res = 0L
while (lo <= hi) {
val mid = lo + (hi - lo) / 2L
val canRun = n * mid <= batteries.asSequence().map { minOf(it.toLong(), mid) }.sum()!!
if (canRun) {
res = mid
lo = mid + 1L
} else hi = mid - 1L
}
return res
}
1870. Minimum Speed to Arrive on Time medium blog post substack
https://t.me/leetcode_daily_unstoppable/287
Max speed
for all dist
departing at round hours, be fit in hour
Given the speed, we can calculate the travel time
in O(n). With decreasing speed the time grows, so we can do the Binary Search
For more robust Binary Search code:
lo
and hi
lo == hi
lo = mid + 1
, hi = mid - 1
res = mid
Time complexity: \(O(nlog(n))\)
Space complexity: \(O(1)\)
fun minSpeedOnTime(dist: IntArray, hour: Double): Int {
var lo = 1
var hi = 1_000_000_000
var res = -1
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
var dt = 0.0
val time = dist.fold(0.0) { r, t ->
r + Math.ceil(dt).also { dt = t / mid.toDouble() }
} + dt
if (hour >= time) {
res = mid
hi = mid - 1
} else lo = mid + 1
}
return res
}
852. Peak Index in a Mountain Array medium blog post substack
https://t.me/leetcode_daily_unstoppable/286
Mountain pattern index
in the array in log time
Do the Binary Search of the biggest growing index
For more robust Binary Search code:
lo
and hi
lo == hi
ind = mid
if conditions are metlo = mid - 1
, hi = mid + 1
Time complexity: \(O(log(n))\)
Space complexity: \(O(1)\)
fun peakIndexInMountainArray(arr: IntArray): Int {
var lo = 1
var hi = arr.lastIndex
var ind = -1
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (arr[mid] > arr[mid - 1]) {
ind = mid
lo = mid + 1
} else hi = mid - 1
}
return ind
}
🌄 "Look at that crimson blush, Alpha!" A radiant sunrise anoints the
towering Everest, its snow-capped peaks aglow with the day's first
light. An ethereal landscape, a symphony of shadows and silhouettes,
lays the stage for an impending adventure. 🏔️
Team Alpha 🥾 chuckles, their voices swallowed by the wind, "Today's
the day we've been dreaming of, Charlie!" Team Charlie 🦅, encased
in their mountain gear, share their excitement. Their eyes, reflecting
the sunlit peaks, are fixated on the summit – their celestial goal.
Base Camps (BC):
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Everest Heights:
1K 2K 3K 4K 5K 6K 7K 8K 9K 10K 11K 12K 13K 14K 15K 16K 15K 14K 13K 12K 11K
🥾(Team Alpha) 🏔️(Mysterious Mid Point) 🦅(Team Charlie)
🧭 "We're off to conquer the Everest!" Alpha's voice reverberates
with a hopeful intensity. Their strategy, an intricate dance with
numbers and ambition – Binary Search. The mountain, its snow-capped
peaks reaching for the skies, hums ancient tales to their eager ears.
BC: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Heights: 1K 2K 3K 4K 5K 6K 7K 8K 9K 10K 11K 12K 13K 14K 15K 16K 15K 14K 13K 12K 11K
🥾🏁(Team Alpha's Milestone) 🦅(Team Charlie)
"10K! Feels like we've captured a bit of heaven," Team Alpha shares
their awe, their voices a mere whisper against the grandeur of the
landscape.
🏞️ But the mountain, a grand enigma, hides her secrets well...
With a sudden, heart-stopping rumble, the mountain shivers, the
seemingly innocuous snow beneath Team Charlie's feet giving way. A
fierce avalanche sweeps down the slopes, sending Charlie scrambling
for cover.
BC: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Heights: 1K 2K 3K 4K 5K 6K 7K 8K 9K 10K 11K 12K 13K 14K 15K 16K 15K 14K 13K 12K 11K
🥾🏁(Team Alpha's Resolve) ❄️🦅(Team Charlie's Setback)
"Avalanche!" Charlie's voice, choked with frosty fear, crackles over
the radio. Yet, Team Alpha, undeterred by the wrath of the mountain,
pushes forward. "Hold tight, Charlie! We're stardust-bound!" They
continue their daring ascent, breaching the cloudline to a dizzying
16K.
🚩 "Charlie, we're among the stars!" Alpha's voice, filled with
joyous triumph, echoes through the radio. The peak is conquered.
BC: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Heights: 1K 2K 3K 4K 5K 6K 7K 8K 9K 10K 11K 12K 13K 14K 15K 16K 15K 14K 13K 12K 11K
🚩🎉(Victorious Summit at BC 15!)
As the sun bathes the snowy peaks in a golden hue, Team Alpha plants
their triumphant flag at the top. They stand there, at the roof of
the world, their hearts swelling with joy and pride. It's the journey,
the shared aspirations, the dream of reaching for the stars, that truly
defines their adventure. 🌠
50. Pow(x, n) medium blog post substack
https://t.me/leetcode_daily_unstoppable/285
x^n
We can use tabulations technique: compute all powers of 2 and reuse them.
// 2 1
// 2*2 = 4 2
// 4*4 = 16 4
// 16*16=256 8
// 2^8 * 2^8 = 2^16 16
// 2^31 = 2^16 * 2^4 * 2
After computing the growing part, we need to find the optimal way to split the reminder
. For example, x^31 = x^16 * x^5, then x^5 = x^4 * x^1. To find the closest power of 2, we can take the most significant bit
, which is an x & -x
bit operation.
// 5 -> 4 101 -> 100
// 7 -> 4 111 -> 100
// 9 -> 8 1001 -> 1000
Int.MIN_VALUE
, as abs(MIN_VALUE) == abs(-MIN_VALUE)
Time complexity: \(O(log(n))\)
Space complexity: \(O(1)\)
fun myPow(x: Double, n: Int): Double {
if (n == 0) return 1.0
val mul = if (n < 0) 1 / x else x
val exp = if (n == Int.MIN_VALUE) Int.MAX_VALUE else Math.abs(n)
val cache = DoubleArray(32)
var k = mul
var f = 1
cache[0] = k
while (f <= exp / 2) {
k = k * k
f = f * 2
cache[Integer.numberOfTrailingZeros(f)] = k
}
while (f < exp) {
val e = exp - f
val pow = e and -e
k = k * cache[Integer.numberOfTrailingZeros(pow)]
f = f + pow
}
if (n == Int.MIN_VALUE) k = k * mul
return k
}
894. All Possible Full Binary Trees medium blog post substack
https://t.me/leetcode_daily_unstoppable/284
All possible Full Binary Trees with n
nodes, each have both children
First, if count of nodes is even
, BFT is not possible.
Let’s observe how the Trees are growing:
There are n / 2
rounds of adding a new pair of nodes to each leaf of each Tree in the latest generation.
Some duplicate trees occur, so we need to calculate a hash
.
Let’s implement it in a BFS manner.
hash
, add some symbols to indicate a level [...]
Time complexity: \(O(n^4 2^n)\), n generations, queue size grows in 2^n manner, count of leafs grows by 1 each generation, so it’s x + (x + 1) + .. + (x + n), giving n^2, another n for collection leafs, and another for hash and clone
Space complexity: \(O(n^3 2^n)\)
fun clone(curr: TreeNode): TreeNode = TreeNode(0).apply {
curr.left?.let { left = clone(it) }
curr.right?.let { right = clone(it) }
}
fun hash(curr: TreeNode): String =
"[${curr.`val`} ${ curr.left?.let { hash(it) } } ${ curr.right?.let { hash(it) } }]"
fun collectLeafs(curr: TreeNode): List<TreeNode> =
if (curr.left == null && curr.right == null) listOf(curr)
else collectLeafs(curr.left!!) + collectLeafs(curr.right!!)
fun allPossibleFBT(n: Int): List<TreeNode?> = if (n % 2 == 0) listOf() else
with (ArrayDeque<TreeNode>().apply { add(TreeNode(0)) }) {
val added = HashSet<String>()
repeat (n / 2) { rep ->
repeat(size) {
val root = poll()
collectLeafs(root).forEach {
it.left = TreeNode(0)
it.right = TreeNode(0)
if (added.add(hash(root))) add(clone(root))
it.left = null
it.right = null
}
}
}
toList()
}
effective solution. It can be described as “for every N generate every possible split of [0..i] [i+1..N]”. Subtrees are also made of all possible combinations.
688. Knight Probability in Chessboard medium blog post substack
https://t.me/leetcode_daily_unstoppable/283
Probability of making k
steps on a chessboard without stepping outside
The description example doesn’t give a clear picture of how the probability works.
1/8
each time we make a step.1/8
, two steps are 1/8 * 1/8
and so on.k-steps
path will have probability of 1/8^k
k-steps
paths, that will remain on a boardk
rounds:queue.size / 8^k
, as queue will contain only the final possible ways after k stepsHowever, there are too many possible ways, we will quickly run out of memory.
It is noticeable, some ways are repeating, and after s
steps the same cell [x, y] produces the same amount of possible ways dp[x, y][s]
. We can cache this result for each cell.
However, the number of ways are still very big and do not fit into Long
64 bits. To solve this, we can cache not only the ways, but the probability
, dividing each step by 8
.
Time complexity: \(O(kn^2)\)
Space complexity: \(O(kn^2)\)
val dxdy = sequenceOf(-2 to 1, -2 to -1, 2 to 1, 2 to -1, -1 to 2, -1 to -2, 1 to 2, 1 to -2)
fun knightProbability(n: Int, k: Int, row: Int, column: Int): Double {
val cache = mutableMapOf<Pair<Pair<Int, Int>, Int>, Double>()
fun count(x: Int, y: Int, k: Int): Double = if (k == 0) 1.0 else cache.getOrPut(x to y to k) {
dxdy.map { (dx, dy) -> x + dx to y + dy }
.map { (x, y) -> if (x in 0..n-1 && y in 0..n-1) count(x, y, k - 1) / 8.0 else 0.0 }
.sum()
}
return count(column, row, k)
}
Step ₀ - The High Noon Duel 🤠🎵🌵:
🎶 The town clock strikes twelve, and the high noon chess duel commences. A
lone knight 🐎 trots onto the scorching, sun-bleached chessboard, casting a long
shadow on the sandy squares.
╔═══🌵═══🌵═══╗
║ 🐎 ║ ║ ║
╠═══🌵═══🌵═══╣
║ ║ ║ ║
╠═══🌵═══🌵═══╣
║ ║ ║ ║
╚═══🌵═══🌵═══╝
The Sheriff 🤠, ever the statistician, watches keenly. "For now, the odds are
all in your favor, Knight," he says, unveiling the initial probability 𝓹₀ = 1.
┌─────💰────┬────💰────┬────💰────┐
│ 1 │ 0 │ 0 │
├─────💰────┼────💰────┼────💰────┤
│ 0 │ 0 │ 0 │
├─────💰────┼────💰────┼────💰────┤
│ 0 │ 0 │ 0 │
└─────💰────┴────💰────┴────💰────┘
Step ₁ - The Dusty Trail 🌄🎵🐴:
🎶 The knight 🐎 leaps into action, stirring up a cloud of dust. He lands in two
different squares, each with a calculated 1/8 chance. The Sheriff 🤠 nods
approvingly. "Bold moves, Knight. The probability after this is 𝓹₁ = 1/8 + 1/8 = 1/4."
╔═══🌵═══🌵═══╗
║ ║ ║ ║
╠═══🌵═══🌵═══╣
║ ║ ║ 🐎 ║
╠═══🌵═══🌵═══╣
║ ║ 🐎 ║ ║
╚═══🌵═══🌵═══╝
He reveals the new odds:
┌─────💰────┬────💰────┬────💰────┐
│ 0 │ 0 │ 0 │
├─────💰────┼────💰────┼────💰────┤
│ 0 │ 0 │ ¹/₈ │
├─────💰────┼────💰────┼────💰────┤
│ 0 │ ¹/₈ │ 0 │
└─────💰────┴────💰────┴────💰────┘
Step ₂ - The Sun-Baked Crossroads ☀️🎵🌪️:
🎶 The knight 🐎 continues his daring maneuvers, hopping onto a few critical
spots. He lands on three squares, with probabilities of 1/64, 1/64, and 2/64.
Adding these up, the Sheriff 🤠 declares, "The stakes have risen, Knight. The
total is 𝓹₂ = 1/64 + 1/64 + 2/64 = 1/16."
╔═══🌵═══🌵═══╗
║🐎🐎║ ║ 🐎 ║
╠═══🌵═══🌵═══╣
║ ║ ║ ║
╠═══🌵═══🌵═══╣
║ 🐎 ║ ║ ║
╚═══🌵═══🌵═══╝
The updated odds take shape:
┌─────💰────┬────💰────┬────💰────┐
│ ²/₆₄ │ 0 │ ¹/₆₄ │
├─────💰────┼────💰────┼────💰────┤
│ 0 │ 0 │ 0 │
├─────💰────┼────💰────┼────💰────┤
│ ¹/₆₄ │ 0 │ 0 │
└─────💰────┴────💰────┴────💰────┘
Step ₃ - The Outlaw's Hideout 🏚️🎵🐍:
🎶 As the sun sets, the knight 🐎 lands in a few hidden spots with various
probabilities. Each calculated leap adds to his total: 1/512 + 1/512 + 3/512 + 3/512.
The Sheriff 🤠 raises an eyebrow. "Well played, Knight. Your total now is 𝓹₃ =
1/512 + 1/512 + 3/512 + 3/512."
╔═══🌵═══🌵═══╗
║ ║ 🐎 ║ ║
╠═══🌵═══🌵═══╣
║ 🐎 ║ ║🐎🐎🐎║
╠═══🌵═══🌵═══╣
║ ║🐎🐎🐎║ ║
╚═══🌵═══🌵═══╝
Beneath the twinkling stars, the Sheriff 🤠 surveys the evolving game. "You're
not an easy one to beat, Knight," he admits, revealing the updated stakes:
┌─────💰────┬────💰────┬────💰────┐
│ 0 │ ¹/₅₁₂ │ 0 │
├─────💰────┼────💰────┼────💰────┤
│ ¹/₅₁₂ │ 0 │ ³/₅₁₂ │
├─────💰────┼────💰────┼────💰────┤
│ 0 │ ³/₅₁₂ │ 0 │
└─────💰────┴────💰────┴────💰────┘
🎶 So, under the twinkling stars and to the tune of the whistling wind, our
knight's adventure continues into the night. The stakes are high, the moves
unpredictable, but one thing's certain: this wild chess duel is far from over! 🌵🐎🌌🎵
673. Number of Longest Increasing Subsequence medium blog post substack
https://t.me/leetcode_daily_unstoppable/282
Count of LIS in an array
To find Longest Increasing Subsequence, there is a known algorithm with \(O(nlog(n))\) time complexity. However, it can help with this case:
3 5 4 7
when we must track both 3 4 7
and 3 5 7
sequences. Given that, we can try to do full search with DFS, taking or skipping a number. To cache some results, we must make dfs
depend on only the input arguments. Let’s define it to return both max length of LIS
and count of them
in one result, and arguments are the starting position in an array and previous number
that we must start sequence from.
Map
gives TLETime complexity: \(O(n^2)\)
Space complexity: \(O(n^2)\)
class R(val maxLen: Int, val cnt: Int)
fun findNumberOfLIS(nums: IntArray): Int {
val cache = Array(nums.size + 1) { Array<R>(nums.size + 2) { R(0, 0) } }
fun dfs(pos: Int, prevPos: Int): R = if (pos == nums.size) R(0, 1) else
cache[pos][prevPos].takeIf { it.cnt != 0 }?: {
val prev = if (prevPos == nums.size) Int.MIN_VALUE else nums[prevPos]
var cnt = 0
while (pos + cnt < nums.size && nums[pos + cnt] == nums[pos]) cnt++
val skip = dfs(pos + cnt, prevPos)
if (nums[pos] <= prev) skip else {
val start = dfs(pos + cnt, pos).let { R(1 + it.maxLen, cnt * it.cnt ) }
if (skip.maxLen == start.maxLen) R(skip.maxLen, start.cnt + skip.cnt)
else if (skip.maxLen > start.maxLen) skip else start
}
}().also { cache[pos][prevPos] = it }
return dfs(0, nums.size).cnt
}
🏰🔮🌌 The Astral Enigma of Eternity
In the boundless tapestry of time, an enigmatic labyrinth 🗝️ whispers
tales of forgotten epochs. Your fateful quest? To decipher the longest
increasing subsequences hidden within the celestial array 🧩 [3, 5, 4, 7].
🌄 The Aurora Gateway: dfs(0, nums.size)
/ \
🌳 The Verdant Passage (dfs(1,0)) / 🌑 The Nebulous Veil (dfs(1,nums.size))
Your odyssey commences at twilight's brink: will you tread the lush
🌳 Verdant Passage or dare to penetrate the enigmatic 🌑 Nebulous Veil?
🌄 The Aurora Gateway: dfs(0, nums.size)
/
🍃 The Glade of Whispers (Pos 1: num[1]=3, dfs(1,0))
/
🌊 The Cascade of Echoes (Pos 2: num[2]=5, dfs(2,1))
/
⛰️ The Bastion of Silence (Pos 3: num[3]=4, dfs(3,2)) 🚫🔒
The labyrinth’s heart pulsates with cryptic riddles. The ⛰️ Bastion of Silence
remains locked, overshadowed by the formidable 🌊 Cascade of Echoes.
🌄 The Aurora Gateway: dfs(0, nums.size)
/
🍃 The Glade of Whispers (Pos 1: num[1]=3, dfs(1,0))
\
🌑 The Phantom of Riddles (Pos 2: num[2]=5, dfs(2,0))
Retracing your footsteps, echoes of untaken paths whisper secrets. Could
the ⛰️ Bastion of Silence hide beneath the enigma of the 🌑 Phantom of Riddles?
🌄 The Aurora Gateway: dfs(0, nums.size)
/
🍃 The Glade of Whispers (Pos 1: num[1]=3, dfs(1,0))
\
💨 The Mist of Mystery (Pos 3: num[3]=4, dfs(3,0))
\
🌩️ The Tempest of Triumph (Pos 4: num[4]=7, dfs(4,3)) 🏁🎉
At last, the tempest yields! Each twist and turn, each riddle spun and
secret learned, illuminates a longest increasing subsequence in the cosmic array.
Your enchanted grimoire 📜✨ (cache) now vibrates with the wisdom of ages:
prevPos\pos 0 1 2 3 4
0 (0,0) (2,1) (2,1) (3,2) (0,0)
1 (0,0) (0,0) (2,1) (3,2) (0,0)
2 (0,0) (0,0) (0,0) (2,1) (0,0)
3 (0,0) (0,0) (0,0) (0,0) (0,0)
4 (0,0) (0,0) (0,0) (0,0) (0,0)
Beneath the shimmering cosmic symphony, you cast the final incantation
🧙♂️ dfs(0, nums.size).cnt. The grimoire blazes with ethereal light, revealing
the total count of longest increasing subsequences.
You emerge from the labyrinth transformed: no longer merely an adventurer,
but the 🌟 Cosmic Guardian of Timeless Wisdom. 🗝️✨🌠
735. Asteroid Collision medium blog post substack
https://t.me/leetcode_daily_unstoppable/281
Result after asteroids collide left-right exploding by size: 15 5 -15 -5 5 -> -15 -5 5
Let’s add positive asteroids to the Stack
. When negative met, it can fly over all smaller positive added, and can explode if larger met.
Kotlin’s API helping reduce some LOC
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun asteroidCollision(asteroids: IntArray): IntArray = with(Stack<Int>()) {
asteroids.forEach { sz ->
if (!generateSequence { if (sz > 0 || isEmpty() || peek() < 0) null else peek() }
.any {
if (it <= -sz) pop()
it >= -sz
}) add(sz)
}
toIntArray()
}
435. Non-overlapping Intervals medium blog post substack
https://t.me/leetcode_daily_unstoppable/280
Minimum intervals to erase overlap
First idea, is to sort the array by from
. Next, we can greedily take intervals and remove overlapping ones. But, to remove the minimum
number, we can start with removing the most long
intervals.
right border
when there is a new non overlapping intervalborder
when it shrinksTime complexity: \(O(nlog(n))\)
Space complexity: \(O(1)\)
fun eraseOverlapIntervals(intervals: Array<IntArray>): Int {
intervals.sortWith(compareBy({ it[0] }))
var border = Int.MIN_VALUE
return intervals.count { (from, to) ->
(border > from).also {
if (border <= from || border > to) border = to
}
}
}
146. LRU Cache medium blog post substack
https://t.me/leetcode_daily_unstoppable/279
We can use Doubly-Linked List representing access time in its order.
firstNode
and lastNode
Time complexity:
\(O(1)\), for each call get
or put
Space complexity: \(O(1)\), for each element
class LRUCache(val capacity: Int) {
class Node(val key: Int, var left: Node? = null, var right: Node? = null)
var size = 0
val map = mutableMapOf<Int, Int>()
val firstNode = Node(-1)
var lastNode = firstNode
val keyToNode = mutableMapOf<Int, Node>()
fun disconnect(node: Node) {
val leftNode = node.left
val rightNode = node.right
node.left = null
node.right = null
leftNode?.right = rightNode
rightNode?.left = leftNode
if (node === lastNode) lastNode = leftNode!!
}
fun updateNode(key: Int) {
val node = keyToNode[key]!!
if (node === lastNode) return
disconnect(node)
lastNode.right = node
node.left = lastNode
lastNode = node
}
fun get(key: Int): Int = map[key]?.also { updateNode(key) } ?: -1
fun put(key: Int, value: Int) {
if (!map.contains(key)) {
if (size == capacity) {
firstNode.right?.let {
map.remove(it.key)
keyToNode.remove(it.key)
disconnect(it)
}
} else size++
keyToNode[key] = Node(key)
}
updateNode(key)
map[key] = value
}
}
445. Add Two Numbers II medium blog post substack
https://t.me/leetcode_daily_unstoppable/278
Linked List of sum of two Linked Lists numbers, 9->9 + 1 = 1->0->0
The hint is in the description: reverse lists, then just do arithmetic. Another way is to use stack.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun addTwoNumbers(l1: ListNode?, l2: ListNode?, n: Int = 0): ListNode? {
fun ListNode?.reverse(): ListNode? {
var curr = this
var prev: ListNode? = null
while (curr != null) {
val next = curr.next
curr.next = prev
prev = curr
curr = next
}
return prev
}
var l1r = l1.reverse()
var l2r = l2.reverse()
var o = 0
var prev: ListNode? = null
while (l1r != null || l2r != null) {
val v = o + (l1r?.`val` ?: 0) + (l2r?.`val` ?: 0)
prev = ListNode(v % 10).apply { next = prev }
o = v / 10
l1r = l1r?.next
l2r = l2r?.next
}
if (o > 0) prev = ListNode(o).apply { next = prev }
l1r.reverse()
l2r.reverse()
return prev
}
1125. Smallest Sufficient Team hard blog post substack
https://t.me/leetcode_daily_unstoppable/277
Smallest team
from people with skills
, having all required skills
The skills set size is less than 32
, so we can compute a bitmask
for each of people
and for the required
skills.
Next, our task is to choose a set from people
that result skills mask will be equal to the required
.
We can do a full search, each time skipping
or adding
one mask from the people
.
Observing the problem, we can see, that result is only depending on the current mask
and all the remaining
people. So, we can cache it.
HashMap
to store skill to index
, but given a small set of skills, just do indexOf
in O(60 * 16)post order
, as dfs
must return only the result depending on the input argumentsTime complexity: \(O(p2^s)\), as full mask bits are 2^s, s - skills, p - people
Space complexity: \(O(p2^s)\)
fun smallestSufficientTeam(skills: Array<String>, people: List<List<String>>): IntArray {
val peoplesMask = people.map { it.fold(0) { r, t -> r or (1 shl skills.indexOf(t)) } }
val cache = mutableMapOf<Pair<Int, Int>, List<Int>>()
fun dfs(curr: Int, mask: Int): List<Int> =
if (mask == (1 shl skills.size) - 1) listOf()
else if (curr == people.size) people.indices.toList()
else cache.getOrPut(curr to mask) {
val skip = dfs(curr + 1, mask)
val take = dfs(curr + 1, mask or peoplesMask[curr]) + curr
if (skip.size < take.size) skip else take
}
return dfs(0, 0).toIntArray()
}
1751. Maximum Number of Events That Can Be Attended II hard blog post substack
https://t.me/leetcode_daily_unstoppable/276
Max sum of at most k
values
from non-intersecting array of (from, to, value)
items
Let’s observe example:
// 0123456789011
// [ 4 ]
// [1][2][3][2]
// [4][2]
If k=1
we choose [4]
if k=2
we choose [4][2]
if k=3
we choose [2][3][2]
k
items we must choose and we must do backtrackingindex
we can pick
or skip
the elementWe can observe, that at any given position the result only depends on the suffix array. That means we can safely cache the result by the current position.
For more robust Binary Search code:
lo
, hi
lo == hi
next = mid
lo = mid + 1
, hi = mid - 1
Time complexity: \(O(nklog(n))\)
Space complexity: \(O(nk)\)
fun maxValue(events: Array<IntArray>, k: Int): Int {
// 0123456789011
// [ 4 ]
// [1][2][3][2]
// [4][2]
val inds = events.indices.sortedWith(compareBy({ events[it][0] }))
// my ideas:
// sort - good
// sweep line ? - wrong
// priority queue ? - wrong
// binary search ? 1..k - wrong
// used hints:
// hint: curr + next vs drop dp?
// hint: binary search next
val cache = mutableMapOf<Pair<Int, Int>, Int>()
fun dfs(curr: Int, canTake: Int): Int {
return if (curr == inds.size || canTake == 0) 0
else cache.getOrPut(curr to canTake) {
val (_, to, value) = events[inds[curr]]
var next = inds.size
var lo = curr + 1
var hi = inds.lastIndex
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
val (nextFrom, _, _) = events[inds[mid]]
if (nextFrom > to) {
next = mid
hi = mid - 1
} else lo = mid + 1
}
maxOf(value + dfs(next, canTake - 1), dfs(curr + 1, canTake))
}
}
return dfs(0, k)
}
1218. Longest Arithmetic Subsequence of Given Difference medium blog post substack
https://t.me/leetcode_daily_unstoppable/275
Longest arithmetic difference
subsequence
Store the next
value and the length
for it.
We can use a HashMap
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun longestSubsequence(arr: IntArray, difference: Int): Int =
with(mutableMapOf<Int, Int>()) {
arr.asSequence().map { x ->
(1 + (this[x] ?: 0)).also { this[x + difference] = it }
}.max()!!
}
207. Course Schedule medium blog post substack
https://t.me/leetcode_daily_unstoppable/274
If none
edges in a cycle
To detect cycle, we can use DFS and two sets cycle
and safe
. Or use Topological Sort and check that all elements are visited.
Let’s use Topological Sort with Breadth-First Search.
indegree
- number of input nodes for each nodeindegree[node] == 0
indegree
as it visitedTime complexity: \(O(VE)\)
Space complexity: \(O(E + V)\)
fun canFinish(numCourses: Int, prerequisites: Array<IntArray>): Boolean {
val fromTo = mutableMapOf<Int, MutableSet<Int>>()
val indegree = IntArray(numCourses)
prerequisites.forEach { (to, from) ->
fromTo.getOrPut(from) { mutableSetOf() } += to
indegree[to]++
}
return with(ArrayDeque<Int>()) {
addAll((0 until numCourses).filter { indegree[it] == 0 })
generateSequence { if (isEmpty()) null else poll() }.map {
fromTo[it]?.forEach {
if (--indegree[it] == 0) add(it)
}
}.count() == numCourses
}
}
802. Find Eventual Safe States medium blog post substack
https://t.me/leetcode_daily_unstoppable/273
List of nodes not in cycles
Simple Depth-First Search will give optimal \(O(n)\) solution.
When handling the visited
set, we must separate those in cycle
and safe
.
cycle
set and add to safe
set in a post-order traversalTime complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun eventualSafeNodes(graph: Array<IntArray>): List<Int> {
val cycle = mutableSetOf<Int>()
val safe = mutableSetOf<Int>()
fun cycle(curr: Int): Boolean {
return if (safe.contains(curr)) false else !cycle.add(curr)
|| graph[curr].any { cycle(it) }
.also {
if (!it) {
cycle.remove(curr)
safe.add(curr)
}
}
}
return graph.indices.filter { !cycle(it) }
}
863. All Nodes Distance K in Binary Tree medium blog post substack
https://t.me/leetcode_daily_unstoppable/272
List of k
distanced from target
nodes in a Binary Tree
There is a one-pass DFS solution, but it feels like too much of a corner cases and result handholding.
A more robust way is to traverse with DFS and connect children nodes to parent, then send a wave from target at k
steps.
Let’s build an undirected graph and do BFS.
HashSet
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun distanceK(root: TreeNode?, target: TreeNode?, k: Int): List<Int> {
val fromTo = mutableMapOf<Int, MutableList<Int>>()
fun dfs(node: TreeNode?, parent: TreeNode?) {
node?.run {
parent?.let {
fromTo.getOrPut(`val`) { mutableListOf() } += it.`val`
fromTo.getOrPut(it.`val`) { mutableListOf() } += `val`
}
dfs(left, this)
dfs(right, this)
}
}
dfs(root, null)
return LinkedList<Int>().apply {
val visited = HashSet<Int>()
target?.run {
add(`val`)
visited.add(`val`)
}
repeat(k) {
repeat(size) {
fromTo.remove(poll())?.forEach { if (visited.add(it)) add(it) }
}
}
}
}
111. Minimum Depth of Binary Tree easy blog post substack
https://t.me/leetcode_daily_unstoppable/271
Count nodes in the shortest path from root to leaf
nodes
, not edges
leaf
is a node without childrenLet’s use BFS
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun minDepth(root: TreeNode?): Int = with(ArrayDeque<TreeNode>()) {
root?.let { add(it) }
generateSequence(1) { (it + 1).takeIf { isNotEmpty() } }
.firstOrNull {
(1..size).any {
with(poll()) {
left?.let { add(it) }
right?.let { add(it) }
left == null && right == null
}
}
} ?: 0
}
2272. Substring With Largest Variance hard blog post substack
https://t.me/leetcode_daily_unstoppable/270
Max diff between count s[i]
and count s[j]
in all substrings of s
The first idea is to simplify the task by considering only two chars, iterating over all alphabet combinations.
Second idea is how to solve this problem for binary string in \(O(n)\): abaabbb
→ abbb
.
We split this problem: find the largest subarray for a
with the smallest count of b
, and reverse the problem – largest b
with smallest a
.
For this issue, there is a Kadane’s algorithm for maximizing sum
: take values greedily and reset count when sum < 0
.
Important customization is to always consider countB
at least 1
as it must be present in a subarray.
Set
of only the chars in s
ab
and ba
pairsTime complexity: \(O(n)\)
Space complexity:
\(O(n)\), or O(1) if asSequence
used
fun largestVariance(s: String): Int = s.toSet()
.let { ss -> ss.map { a -> ss.filter { it != a }.map { a to it } }.flatten() }
.map { (a, b) ->
var countA = 0
var countB = 0
s.filter { it == a || it == b }
.map { c ->
if (c == a) countA++ else countB++
if (countA < countB) {
countA = 0
countB = 0
}
countA - maxOf(1, countB)
}.max() ?: 0
}.max() ?: 0
2551. Put Marbles in Bags hard blog post substack
https://t.me/leetcode_daily_unstoppable/269
abs(max - min)
, where max
and min
are the sum of k
interval borders
Let’s observe some examples:
// 1 3 2 3 5 4 5 7 6
// * * *
// 1+3 2+2 3+6 = 4+4+9 = 17
// * * *
// 1+1 3+3 2+6 = 2+6+8 = 16
// * * * = 1+5 7+7 6+6
// 1 9 1 9 1 9 1 9 1 k = 3
// * * * s = 1+9+1+9+1+1
// * * * s = 1+1+9+1+9+1
// 1 1 9 9 1 1 9 9 1 k = 3
// * * * s = 1+1+1+1+1+1
// * * * s = 1+9+9+9+9+1
// 1 1 1 9 1 9 9 9 1 k = 3
// * * * s = 1+1+1+1+1+1
// * . * * s = 1+9+9+9+9+1
// 1 4 2 5 2 k = 3
// . * . * 1+1+4+2+5+2
// . * * 1+4+2+2+5+2
// . * . * 1+1+4+5+2+2
One thing to note, we must choose k-1
border pairs i-1, i
with min
or max
sum.
Let’s use PriorityQueue.
Time complexity: \(O(nlog(k))\)
Space complexity: \(O(k)\)
fun putMarbles(weights: IntArray, k: Int): Long {
val pqMax = PriorityQueue<Int>(compareBy( { weights[it].toLong() + weights[it - 1].toLong() } ))
val pqMin = PriorityQueue<Int>(compareByDescending( { weights[it].toLong() + weights[it - 1].toLong() } ))
for (i in 1..weights.lastIndex) {
pqMax.add(i)
if (pqMax.size > k - 1) pqMax.poll()
pqMin.add(i)
if (pqMin.size > k - 1) pqMin.poll()
}
return Math.abs(pqMax.map { weights[it].toLong() + weights[it - 1].toLong() }.sum()!! -
pqMin.map { weights[it].toLong() + weights[it - 1].toLong() }.sum()!!)
}
2024. Maximize the Confusion of an Exam medium blog post substack
https://t.me/leetcode_daily_unstoppable/268
Max same letter subarray replacing k
letters
An important example is ftftftft k=3
: we must fill all the intervals. It also tells, after each filling up we must decrease k
. Let’s count T
and F
.
Sliding window is valid when tt <= k || ff <= k
.
We can save some lines using Kotlin collections API
Time complexity: \(O(n)\)
Space complexity:
\(O(n)\), or \(O(1)\) using asSequence
fun maxConsecutiveAnswers(answerKey: String, k: Int): Int {
var tt = 0
var ff = 0
var lo = 0
return answerKey.mapIndexed { i, c ->
if (c == 'T') tt++ else ff++
while (tt > k && ff > k && lo < i)
if (answerKey[lo++] == 'T') tt-- else ff--
i - lo + 1
}.max() ?: 0
}
209. Minimum Size Subarray Sum medium blog post substack
https://t.me/leetcode_daily_unstoppable/267
Min length subarray with sum >= target
Use two pointers: one adding to sum
and another subtracting. As all numbers are positive, then sum
will always be increasing with adding a number and deceasing when subtracting.
Let’s use Kotlin Sequence
API
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun minSubArrayLen(target: Int, nums: IntArray): Int {
var lo = 0
var sum = 0
return nums.asSequence().mapIndexed { hi, n ->
sum += n
while (sum - nums[lo] >= target) sum -= nums[lo++]
(hi - lo + 1).takeIf { sum >= target }
}
.filterNotNull()
.min() ?: 0
}
1493. Longest Subarray of 1’s After Deleting One Element medium blog post substack
https://t.me/leetcode_daily_unstoppable/266
Largest 1..1
subarray after removing one item
Let’s maintain two pointers for a start
and a nextStart
positions, and a third pointer for the right
border.
start
to the nextStart
when right
== 0nextStart
to start of 1
’s1
’s, as we must remove 1
then anywayTime complexity: \(O(n)\)
Space complexity:
\(O(n)\) add asSequence
for it to become \(O(1)\)
fun longestSubarray(nums: IntArray): Int {
var start = -1
var nextStart = -1
return if (nums.sum() == nums.size) nums.size - 1
else nums.mapIndexed { i, n ->
if (n == 0) {
start = nextStart
nextStart = -1
0
} else {
if (nextStart == -1) nextStart = i
if (start == -1) start = nextStart
i - start + (if (start == nextStart) 1 else 0)
}
}.max() ?:0
}
137. Single Number II medium blog post substack
https://t.me/leetcode_daily_unstoppable/265
Single number in an array of tripples
One simple approach it to count bits at each position.
Result will have a 1
when count % 3 != 0
.
Let’s use fold.
Time complexity: \(O(n)\)
Space complexity: \(O(1)\)
fun singleNumber(nums: IntArray): Int =
//110
//110
//110
//001
//001
//001
//010
//010
//010
//100
//463
(0..31).fold(0) { res, bit ->
res or ((nums.count { 0 != it and (1 shl bit) } % 3) shl bit)
}
859. Buddy Strings easy blog post substack
https://t.me/leetcode_daily_unstoppable/264
Is it just one swap s[i]<>s[j]
to string s
== string goal
Compare two strings for each position. There are must be only two not equal positions and they must be mirrored pairs.
Let’s write it in Kotlin collections API style.
Time complexity: \(O(n)\)
Space complexity: \(O(n)\)
fun buddyStrings(s: String, goal: String): Boolean = s.length == goal.length && (
s == goal && s.groupBy { it }.any { it.value.size > 1 } ||
s.zip(goal)
.filter { (a, b) -> a != b }
.windowed(2)
.map { (ab, cd) -> listOf(ab, cd.second to cd.first) }
.let { it.size == 1 && it[0][0] == it[0][1] }
)
1601. Maximum Number of Achievable Transfer Requests hard blog post substack
https://t.me/leetcode_daily_unstoppable/263
Max edges to make all counts in == out
edges in graph
Let’s observe some examples:
All requests are valid if count of incoming edges are equal to outcoming. One possible solution is to just check each combination of edges.
Let’s use bitmask to traverse all combinations, as total number 16
can fit in Int
Time complexity: \(O(n2^r)\)
Space complexity: \(O(n2^r)\)
fun maximumRequests(n: Int, requests: Array<IntArray>): Int =
(0..((1 shl requests.size) - 1)).filter { mask ->
val fromTo = IntArray(n)
requests.indices.filter { ((1 shl it) and mask) != 0 }.forEach {
val (from, to) = requests[it]
fromTo[from] -= 1
fromTo[to] += 1
}
fromTo.all { it == 0 }
}.map { Integer.bitCount(it) }.max()!!
2305. Fair Distribution of Cookies medium blog post substack
https://t.me/leetcode_daily_unstoppable/262
Min
of the max
distributing n
cookies to k
children
Search all possible ways to give current cookie to one of the children. Backtrack sums and calculate the result.
Just DFS
Time complexity: \(O(k^n)\)
Space complexity: \(O(2^n)\)
fun distributeCookies(cookies: IntArray, k: Int): Int {
fun dfs(pos: Int, children: IntArray): Int {
if (pos == cookies.size) return if (children.contains(0)) -1 else children.max()!!
var min = -1
for (i in 0 until k) {
children[i] += cookies[pos]
val res = dfs(pos + 1, children)
if (res != -1) min = if (min == -1) res else minOf(min, res)
children[i] -= cookies[pos]
}
return min
}
return dfs(0, IntArray(k))
}
1970. Last Day Where You Can Still Cross hard blog post substack
https://t.me/leetcode_daily_unstoppable/261
Last day
matrix connected top-bottom when flooded each day at cells[day]
One possible solution is to do a Binary Search in a days space, however it gives TLE.
Let’s invert the problem: find the first day from the end where there is a connection top-bottom.
Now, cells[day]
is a new ground. We can use Union-Find to connect ground cells.
top
and bottom
uf[n] = x
Time complexity:
\(O(an)\), where a
is a reverse Ackerman function
Space complexity: \(O(n)\)
val uf = HashMap<Int, Int>()
fun root(x: Int): Int = if (uf[x] == null || uf[x] == x) x else root(uf[x]!!)
.also { uf[x] = it }
fun latestDayToCross(row: Int, col: Int, cells: Array<IntArray>) =
cells.size - 1 - cells.reversed().indexOfFirst { (y, x) ->
uf[y * col + x] = root(if (y == 1) 0 else if (y == row) 1 else y * col + x)
sequenceOf(y to x - 1, y to x + 1, y - 1 to x, y + 1 to x)
.filter { (y, x) -> y in 1..row && x in 1..col }
.map { (y, x) -> y * col + x }
.forEach { if (uf[it] != null) uf[root(y * col + x)] = root(it) }
root(0) == root(1)
}
864. Shortest Path to Get All Keys hard blog post substack
https://t.me/leetcode_daily_unstoppable/260
Min steps to collect all lowercase
keys in matrix. #
and uppercase
locks are blockers.
What will not work:
For the shortest path, we can make a Breadth-First Search wave in a space of the current position and collected keys set.
(1 << countKeys) - 1
Time complexity: \(O(nm2^k)\)
Space complexity: \(O(nm2^k)\)
val dir = arrayOf(0, 1, 0, -1)
data class Step(val y: Int, val x: Int, val keys: Int)
fun shortestPathAllKeys(grid: Array<String>): Int {
val w = grid[0].length
val s = (0..grid.size * w).first { '@' == grid[it / w][it % w] }
val bit: (Char) -> Int = { 1 shl (it.toLowerCase().toInt() - 'a'.toInt()) }
val visited = HashSet<Step>()
val allKeys = (1 shl (grid.map { it.count { it.isLowerCase() } }.sum()!!)) - 1
var steps = -1
return with(ArrayDeque<Step>()) {
add(Step(s / w, s % w, 0))
while (isNotEmpty() && steps++ < grid.size * w) {
repeat(size) {
val step = poll()
val (y, x, keys) = step
if (keys == allKeys) return steps - 1
if (x in 0 until w && y in 0..grid.lastIndex && visited.add(step)) {
val cell = grid[y][x]
if (cell != '#' && !(cell.isUpperCase() && 0 == (keys and bit(cell)))) {
val newKeys = if (cell.isLowerCase()) (keys or bit(cell)) else keys
var dx = -1
dir.forEach { dy -> add(Step(y + dy, x + dx, newKeys)).also { dx = dy } }
}
}
}
}
-1
}
}
1514. Path with Maximum Probability medium blog post substack
https://t.me/leetcode_daily_unstoppable/259
Max probability path from start
to end
in a probability edges graph
What didn’t work:
visited
set - will not work, as we need to visit some nodes several timesstart
to every node in an arraybetter
pathTime complexity: \(O(EV)\)
Space complexity: \(O(EV)\)
fun maxProbability(n: Int, edges: Array<IntArray>, succProb: DoubleArray, start: Int, end: Int): Double {
val pstart = Array(n) { 0.0 }
val adj = mutableMapOf<Int, MutableList<Pair<Int, Double>>>()
edges.forEachIndexed { i, (from, to) ->
adj.getOrPut(from) { mutableListOf() } += to to succProb[i]
adj.getOrPut(to) { mutableListOf() } += from to succProb[i]
}
with(ArrayDeque<Pair<Int, Double>>()) {
add(start to 1.0)
while(isNotEmpty()) {
val (curr, p) = poll()
if (p <= pstart[curr]) continue
pstart[curr] = p
adj[curr]?.forEach { (next, pnext) -> add(next to p * pnext) }
}
}
return pstart[end]
}
373. Find K Pairs with Smallest Sums medium blog post substack
https://t.me/leetcode_daily_unstoppable/258
List of increasing sum pairs a[i], b[j]
from two sorted lists a, b
Naive solution with two pointers didn’t work, as we must backtrack to the previous pointers sometimes:
1 1 2
1 2 3
1+1 1+1 2+1 2+2(?) vs 1+2
The trick is to think of the pairs i,j
as graph nodes, where the adjacent list is i+1,j
and i, j+1
. Each next node sum is strictly greater than the previous:
Now we can walk this graph in exactly k
steps with Dijkstra algorithm using PriorityQueue
to find the next smallest node.
visited
setgenerateSequence
Time complexity:
\(O(klogk)\), there are k
steps to peek from heap of size k
Space complexity: \(O(k)\)
fun kSmallestPairs(nums1: IntArray, nums2: IntArray, k: Int): List<List<Int>> =
with(PriorityQueue<List<Int>>(compareBy({ nums1[it[0]].toLong() + nums2[it[1]].toLong() }))) {
add(listOf(0, 0))
val visited = HashSet<Pair<Int, Int>>()
visited.add(0 to 0)
generateSequence {
val (i, j) = poll()
if (i < nums1.lastIndex && visited.add(i + 1 to j)) add(listOf(i + 1, j))
if (j < nums2.lastIndex && visited.add(i to j + 1)) add(listOf(i, j + 1))
listOf(nums1[i], nums2[j])
}
.take(minOf(k.toLong(), nums1.size.toLong() * nums2.size.toLong()).toInt())
.toList()
}
2462. Total Cost to Hire K Workers medium blog post substack
https://t.me/leetcode_daily_unstoppable/257
The sum of the smallest cost from suffix and prefix of a costs
size of candidates
in k
iterations
Description of the problem is rather ambiguous: we actually need to consider candidates
count of items from the head and from the tail of the costs
array. Then we can use PriorityQueue
to choose the minimum and adjust two pointers lo
and hi
.
2 * candidates >= costs.size
takenL
and takenR
or just use queue’s sizes to minify the codeTime complexity: \(O(nlog(n))\)
Space complexity: \(O(n)\)
fun totalCost(costs: IntArray, k: Int, candidates: Int): Long {
val pqL = PriorityQueue<Int>()
val pqR = PriorityQueue<Int>()
var lo = 0
var hi = costs.lastIndex
var sum = 0L
var count = 0
if (2 * candidates >= costs.size) while (lo <= hi) pqL.add(costs[lo++])
while (pqL.size < candidates && lo <= hi) pqL.add(costs[lo++])
while (pqR.size < candidates && lo < hi) pqR.add(costs[hi--])
while (lo <= hi && count++ < k) {
if (pqR.peek() < pqL.peek()) {
sum += pqR.poll()
pqR.add(costs[hi--])
} else {
sum += pqL.poll()
pqL.add(costs[lo++])
}
}
while (pqR.isNotEmpty()) pqL.add(pqR.poll())
while (count++ < k && pqL.isNotEmpty()) sum += pqL.poll()
return sum
}
1575. Count All Possible Routes hard blog post substack
https://t.me/leetcode_daily_unstoppable/256
Count paths from start
to finish
using |locations[i]-locations[j]
of the fuel
Let’s observe the example:
// 0 1 2 3 4
// 2 3 6 8 4
// * *
//
// 2 3 4 6 8
// * *
//
// 3-2(4)-3(3)-6(0)
// 3-6(2)-8(0)
// 3-8(5)
// 3-8(5)-6(3)-8(1)
// 3-4(4)-6(2)-8(0)
At each position curr
given the amount of fuel f
there is a certain number of ways to finish
. It is independent of all the other factors, so can be safely cached.
finish
to finish
, modify the code to search other paths when finish
is reachedTime complexity:
\(O(nf)\), f
- is a max fuel
Space complexity: \(O(nf)\)
fun countRoutes(locations: IntArray, start: Int, finish: Int, fuel: Int): Int {
// 0 1 2 3 4
// 2 3 6 8 4
// * *
//
// 2 3 4 6 8
// * *
//
// 3-2(4)-3(3)-6(0)
// 3-6(2)-8(0)
// 3-8(5)
// 3-8(5)-6(3)-8(1)
// 3-4(4)-6(2)-8(0)
val cache = mutableMapOf<Pair<Int, Int>, Int>()
fun dfs(curr: Int, f: Int): Int {
if (f < 0) return 0
return cache.getOrPut(curr to f) {
var sum = if (curr == finish) 1 else 0
locations.forEachIndexed { i, n ->
if (i != curr) {
sum = (sum + dfs(i, f - Math.abs(n - locations[curr]))) % 1_000_000_007
}
}
return@getOrPut sum
}
}
return dfs(start, fuel)
}
956. Tallest Billboard hard blog post substack
https://t.me/leetcode_daily_unstoppable/255
Max sum of disjoint set in array
Naive Dynamic Programming solution is to do a full search, adding to the first and to the second sums. That will give Out of Memory for this problem constraints.
dp[i][firstSum][secondSum] -> Out of Memory
The trick to make it work and consume less memory, is to cache only the difference firstSum - secondSum
. It will slightly modify the code, but the principle is the same: try to add to the first, then to the second, otherwise skip.
diff == 0
then sum1 == sum2
Time complexity:
\(O(nm)\), m
is a max difference
Space complexity: \(O(nm)\)
fun tallestBillboard(rods: IntArray): Int {
val cache = Array(rods.size + 1) { Array(10000) { -1 } }
fun dfs(curr: Int, sumDiff: Int): Int {
if (curr == rods.size) return if (sumDiff == 0) 0 else Int.MIN_VALUE / 2
return cache[curr][sumDiff + 5000].takeIf { it != -1 } ?: {
val take1 = rods[curr] + dfs(curr + 1, sumDiff + rods[curr])
val take2 = dfs(curr + 1, sumDiff - rods[curr])
val notTake = dfs(curr + 1, sumDiff)
maxOf(take1, take2, notTake)
}().also { cache[curr][sumDiff + 5000] = it }
}
return dfs(0, 0)
}
1027. Longest Arithmetic Subsequence medium blog post substack
https://t.me/leetcode_daily_unstoppable/254
Max arithmetic subsequence length in array
This was a hard problem for me :) Naive Dynamic Programming solution with recursion and cache will give TLE. Let’s observe the result, adding numbers one-by-one:
// 20 1 15 3 10 5 8
// 20
// 20 1
// 1
// 20 20 1 15
// 1 15 15
//
// 20 20 20 1 1 15 3
// 1 15 3 15 3 3
//
// 20 20 20 20 1 1 1 15 15 10
// 1 15 3 10 15 3 10 3 10
// 10
//
// 20 20 20 20 20 1 1 1 1 15 15 15 10 5
// 1 15 3 10 5 15 3 10 5 3 10 5 5
// 10 5
// 5
//
// 20 20 20 20 20 20 1 1 1 1 1 15 15 15 15 10 10 5 8
// 1 15 3 10 5 8 15 3 10 5 8 3 10 5 8 5 8 8
// 10 5
For each pair from-to
there is a sequence. When adding another number, we know what next
numbers are expected.
We can put those sequences in a HashMap
by next
number key.
data class R(var next: Int, val d: Int, var size: Int)
fun longestArithSeqLength(nums: IntArray): Int {
// 20 1 15 3 10 5 8
// 20
// 20 1
// 1
// 20 20 1 15
// 1 15 15
//
// 20 20 20 1 1 15 3
// 1 15 3 15 3 3
//
// 20 20 20 20 1 1 1 15 15 10
// 1 15 3 10 15 3 10 3 10
// 10
//
// 20 20 20 20 20 1 1 1 1 15 15 15 10 5
// 1 15 3 10 5 15 3 10 5 3 10 5 5
// 10 5
// 5
//
// 20 20 20 20 20 20 1 1 1 1 1 15 15 15 15 10 10 5 8
// 1 15 3 10 5 8 15 3 10 5 8 3 10 5 8 5 8 8
// 10 5
val nextToR = mutableMapOf<Int, MutableList<R>>()
var max = 2
nums.forEachIndexed { to, num ->
nextToR.remove(num)?.forEach { r ->
r.next = num + r.d
max = maxOf(max, ++r.size)
nextToR.getOrPut(r.next) { mutableListOf() } += r
}
for (from in 0..to - 1) {
val d = num - nums[from]
val next = num + d
nextToR.getOrPut(next) { mutableListOf() } += R(next, d, 2)
}
}
return max
}
714. Best Time to Buy and Sell Stock with Transaction Fee medium blog post substack
https://t.me/leetcode_daily_unstoppable/253
Max profit from buying stocks and selling them with fee
for prices[day]
Naive recursive or iterative Dynamic Programming solution will take \(O(n^2)\) time if we iterate over all days for buying and for selling.
The trick here is to consider the money balances you have each day. We can track two separate money balances: for when we’re buying the stock balanceBuy
and for when we’re selling balanceSell
. Then, it is simple to greedily track balances:
prices[day]
from balanceBuy
prices[day] - fee
to balanceSell
buy-sell-buy-sell..
, or we can rewrite this like currentBalance = maxOf(balanceSell, balanceBuy)
and use it for addition and subtraction.
fun maxProfit(prices: IntArray, fee: Int) = prices
.fold(-prices[0] to 0) { (balanceBuy, balance), price ->
maxOf(balanceBuy, balance - price) to maxOf(balance, balanceBuy + price - fee)
}.second
2448. Minimum Cost to Make Array Equal hard blog post substack
https://t.me/leetcode_daily_unstoppable/252
Min cost to make all arr[i]
equal, where each change is cost[i]
First idea is that at least one element can be unchanged. Assume, that we want to keep the most costly element unchanged, but this will break on example:
1 2 2 2 2 1 1 1
f(1) = 0 + 1 + 1 + 1 = 3
f(2) = 2 + 0 + 0 + 0 = 2 <-- more optimal
Let’s observe the resulting cost for each number:
// 1 2 3 2 1 2 1 1 1 1
//0: 2 2 3 2 1 = 10
//1: 0 1 2 1 0 = 4
//2: 2 0 1 0 1 = 4
//3: 4 1 0 1 2 = 8
//4: 6 2 1 2 3 = 14
We can see that f(x)
have a minimum and is continuous. We can find it with Binary Search, comparing the slope = f(mid + 1) - f(mid - 1)
. If slope > 0
, minimum is on the left.
For more robust Binary Search:
lo
, hi
min
lo = mid + 1
or hi = mid - 1
lo == hi
fun minCost(nums: IntArray, cost: IntArray): Long {
// 1 2 3 2 1 2 1 1 1 1
//0: 2 2 3 2 1 = 10
//1: 0 1 2 1 0 = 4
//2: 2 0 1 0 1 = 4
//3: 4 1 0 1 2 = 8
//4: 6 2 1 2 3 = 14
fun costTo(x: Long): Long {
return nums.indices.map { Math.abs(nums[it].toLong() - x) * cost[it].toLong() }.sum()
}
var lo = nums.min()?.toLong() ?: 0L
var hi = nums.max()?.toLong() ?: 0L
var min = costTo(lo)
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
val costMid1 = costTo(mid - 1)
val costMid2 = costTo(mid + 1)
min = minOf(min, costMid1, costMid2)
if (costMid1 < costMid2) hi = mid - 1 else lo = mid + 1
}
return min
}
2090. K Radius Subarray Averages medium blog post substack
https://t.me/leetcode_daily_unstoppable/251
Array containing sliding window of size 2k+1
average or -1
Just do what is asked
Int
overflow
fun getAverages(nums: IntArray, k: Int): IntArray {
if (k == 0) return nums
var sum = 0L
val res = IntArray(nums.size) { -1 }
for (i in 0 until nums.size) {
sum += nums[i]
if (i > 2 * k) sum -= nums[i - 2 * k - 1]
if (i >= 2 * k) res[i - k] = (sum / (2 * k + 1)).toInt()
}
return res
}
1732. Find the Highest Altitude easy blog post substack
https://t.me/leetcode_daily_unstoppable/250
Max running sum
Just sum all the values and compute the max
Let’s write Kotlin fold
one-liner
fun largestAltitude(gain: IntArray): Int = gain
.fold(0 to 0) { (max, sum), t -> maxOf(max, sum + t) to (sum + t) }
.first
2328. Number of Increasing Paths in a Grid hard blog post substack
https://t.me/leetcode_daily_unstoppable/249
Count increasing paths in a matrix
For every cell in a matrix, we can calculate how many increasing paths are starting from it. This result can be memorized. If we know the sibling’s result, then we add it to the current if curr > sibl
.
LongArray
for the memo
fun countPaths(grid: Array<IntArray>): Int {
val m = 1_000_000_007L
val counts = Array(grid.size) { LongArray(grid[0].size) }
fun dfs(y: Int, x: Int): Long {
return counts[y][x].takeIf { it != 0L } ?: {
val v = grid[y][x]
var sum = 1L
if (x > 0 && v > grid[y][x - 1]) sum = (sum + dfs(y, x - 1)) % m
if (y > 0 && v > grid[y - 1][x]) sum = (sum + dfs(y - 1, x)) % m
if (y < grid.size - 1 && v > grid[y + 1][x]) sum = (sum + dfs(y + 1, x)) % m
if (x < grid[0].size - 1 && v > grid[y][x + 1]) sum = (sum + dfs(y, x + 1)) % m
sum
}().also { counts[y][x] = it }
}
return (0 until grid.size * grid[0].size)
.fold(0L) { r, t -> (r + dfs(t / grid[0].size, t % grid[0].size)) % m }
.toInt()
}
1187. Make Array Strictly Increasing hard blog post substack
https://t.me/leetcode_daily_unstoppable/248
Minimum replacements to make arr1
increasing using any numbers arr2
For any current position in arr1
we can leave this number or replace it with any number from arr2[i] > curr
. We can write Depth-First Search to check all possible replacements. To memorize, we must also consider the previous value. It can be used as-is, but more optimally, we just store a skipped
boolean flag and restore the prev
value: if it was skipped, then previous is from arr1
else from arr2
.
arr2
Array
for cache, as it will be faster than a HashMap
arr1
passed, then result it good
fun makeArrayIncreasing(arr1: IntArray, arr2: IntArray): Int {
val list2 = arr2.distinct().sorted()
val INV = -1
val cache = Array(arr1.size + 1) { Array(list2.size + 1) { IntArray(2) { -2 } } }
fun dfs(pos1: Int, pos2: Int, skipped: Int): Int {
val prev = if (skipped == 1) arr1.getOrNull(pos1-1)?:-1 else list2.getOrNull(pos2-1)?:-1
return if (pos1 == arr1.size) 0 else cache[pos1][pos2][skipped].takeIf { it != -2} ?:
if (pos2 == list2.size) {
if (arr1[pos1] > prev) dfs(pos1 + 1, pos2, 1) else INV
} else if (list2[pos2] <= prev) {
dfs(pos1, pos2 + 1, 1)
} else {
val replace = dfs(pos1 + 1, pos2 + 1, 0)
val skip = if (arr1[pos1] > prev) dfs(pos1 + 1, pos2, 1) else INV
if (skip != INV && replace != INV) minOf(skip, 1 + replace)
else if (replace != INV) 1 + replace else skip
}.also { cache[pos1][pos2][skipped] = it }
}
return dfs(0, 0, 1)
}
1569. Number of Ways to Reorder Array to Get Same BST hard blog post substack
https://t.me/leetcode_daily_unstoppable/247
Count permutations of an array with identical Binary Search Tree
First step is to build a Binary Search Tree by adding the elements one by one.
Let’s observe what enables the permutations in [34512]
:
Left child [12]
don’t have permutations, as 1
must be followed by 2
. Same for the right [45]
. However, when we’re merging left and right, they can be merged in different positions.
Let’s observe the pattern for merging ab
x cde
, ab
x cd
, ab
x c
, a
x b
:
And another, abc
x def
:
For each length
of a left len1
and right len2
subtree, we can derive the equation for permutations p
:
\(p(len1, len2) = p(len1 - 1, len2) + p(len1, len2 - 1)\)
Also, when left or right subtree have several permutations, like abc
, acb
, cab
, and right def
, dfe
, the result will be multiplied 3 x 2
.
Build the tree, then compute the p = left.p * right.p * p(left.len, right.len)
in a DFS.
f
class Node(val v: Int, var left: Node? = null, var right: Node? = null)
data class R(val perms: Long, val len: Long)
fun numOfWays(nums: IntArray): Int {
val mod = 1_000_000_007L
var root: Node? = null
fun insert(n: Node?, v: Int): Node {
if (n == null) return Node(v)
if (v > n.v) n.right = insert(n.right, v)
else n.left = insert(n.left, v)
return n
}
nums.forEach { root = insert(root, it) }
val cache = mutableMapOf<Pair<Long, Long>, Long>()
fun f(a: Long, b: Long): Long {
return if (a < b) f(b, a) else if (a <= 0 || b <= 0) 1 else cache.getOrPut(a to b) {
(f(a - 1, b) + f(a, b - 1)) % mod
}
}
fun perms(a: R, b: R): Long {
val perms = (a.perms * b.perms) % mod
return (perms * f(a.len , b.len)) % mod
}
fun dfs(n: Node?): R {
if (n == null) return R(1, 0)
val left = dfs(n.left)
val right = dfs(n.right)
return R(perms(left, right), left.len + right.len + 1)
}
val res = dfs(root)?.perms?.dec() ?: 0
return (if (res < 0) res + mod else res).toInt()
}
1161. Maximum Level Sum of a Binary Tree medium blog post substack
https://t.me/leetcode_daily_unstoppable/246
Binary Tree level with max sum
We can use Breadth-First Search to find a sum
of each level.
Let’s try to write it in a Kotlin style
fun maxLevelSum(root: TreeNode?) = with(ArrayDeque<TreeNode>()) {
root?.let { add(it) }
generateSequence<Int> {
if (isEmpty()) null else (1..size).map {
with(poll()) {
`val`.also {
left?.let { add(it) }
right?.let { add(it) }
}
}
}.sum()
}.withIndex().maxBy { it.value }?.index?.inc() ?: 0
}
530. Minimum Absolute Difference in BST easy blog post substack
https://t.me/leetcode_daily_unstoppable/245
Min difference in a BST
In-order traversal in a BST gives a sorted order, we can compare curr - prev
.
Let’s write a Morris traversal: make the current node a rightmost child of its left child.
fun getMinimumDifference(root: TreeNode?): Int {
if (root == null) return 0
var minDiff = Int.MAX_VALUE
var curr = root
var prev = -1
while (curr != null) {
val left = curr.left
if (left != null) {
var leftRight = left
while (leftRight.right != null) leftRight = leftRight.right
leftRight.right = curr
curr.left = null
curr = left
} else {
if (prev >= 0) minDiff = minOf(minDiff, curr.`val` - prev)
prev = curr.`val`
curr = curr.right
}
}
return minDiff
}
2352. Equal Row and Column Pairs medium blog post substack
https://t.me/leetcode_daily_unstoppable/244
Count of rowArray
== colArray
in an n x n
matrix.
Compute hash
function for each row
and each col
, then compare them. If hash(row) == hash(col)
, then compare arrays.
For hashing, we can use simple 31 * prev + curr
, that encodes both value and position.
tan
hash works perfectly, we can skip comparing the arrays.
fun equalPairs(grid: Array<IntArray>): Int {
val rowHashes = grid.map { it.fold(0.0) { r, t -> Math.tan(r) + t } }
val colHashes = (0..grid.lastIndex).map { x ->
(0..grid.lastIndex).fold(0.0) { r, t -> Math.tan(r) + grid[t][x] } }
return (0..grid.size * grid.size - 1).count {
rowHashes[it / grid.size] == colHashes[it % grid.size]
}
}
228. Summary Ranges easy blog post substack
https://t.me/leetcode_daily_unstoppable/243
Fold continues ranges in a sorted array 1 2 3 5
-> 1->3, 5
Scan from start to end, modify the last interval or add a new one.
Let’s write a Kotlin one-liner
fun summaryRanges(nums: IntArray): List<String> = nums
.fold(mutableListOf<IntArray>()) { r, t ->
if (r.isEmpty() || r.last()[1] + 1 < t) r += intArrayOf(t, t)
else r.last()[1] = t
r
}
.map { (f, t) -> if (f == t) "$f" else "$f->$t"}
1146. Snapshot Array medium blog post substack
https://t.me/leetcode_daily_unstoppable/242
Implement an array where all elements can be saved into a `snapshot’s.
Consider example:
// 0 1 2 3 4 5 6 <-- snapshot id
// 1 . . 2 . . 3 <-- value
When get()(2)
called, 1
must be returned. So, we need to keep all the previous values. We can put them into a list combining with the current snapshot id
: (1,0), (2, 3), (3, 6)
. Then we can do a Binary Search and find the highest_id >= id
.
For more robust Binary Search:
lo
, hi
lo == hi
ind = mid
get
class SnapshotArray(length: Int) {
// 0 1 2 3 4 5 6
// 1 . . 2 . . 3
val arr = Array<MutableList<Pair<Int, Int>>>(length) { mutableListOf() }
var currId = 0
fun set(index: Int, v: Int) {
val idVs = arr[index]
if (idVs.isEmpty() || idVs.last().first != currId) idVs += currId to v
else idVs[idVs.lastIndex] = currId to v
}
fun snap(): Int = currId.also { currId++ }
fun get(index: Int, id: Int): Int {
var lo = 0
var hi = arr[index].lastIndex
var ind = -1
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (arr[index][mid].first <= id) {
ind = mid
lo = mid + 1
} else hi = mid - 1
}
return if (ind == -1) 0 else arr[index][ind].second
}
}
1802. Maximum Value at a Given Index in a Bounded Array medium blog post substack
https://t.me/leetcode_daily_unstoppable/241
Max at index
in an n
sized array, where sum <= maxSum
, nums[i] > 0
and maxDiff(i, i+1) < 2
.
Let’s write possible numbers, for example:
// n=6, i=1, m=10
// 10/6 = 1
// 0 1 2 3 4 5
// -----------
// 0 1 0 0 0 0 sum = 1
// 1 2 1 0 0 0 sum = 1 + (1 + 1 + 1) = 4
// 2 3 2 1 0 0 sum = 4 + (1 + 2 + 1) = 8
// 3 4 3 2 1 0 sum = 8 + (1 + 3 + 1) = 13 > 10 prev + (1 + left + right)
// 4 5 4 3 2 1 sum = 13 + (1 + 4 + 1) = 19 left = minOf(left, i)
// 5 6 5 4 3 2 sum = 19 + (1 + 4 + 1) = 24 right = minOf(right, size - i - 1)
// 6 7 6 5 4 3
// ...
// 5+x sum = 19 + x * (1 + 4 +1)
// ...
// S(x-1) - S(x-1-i) + x + S(x-1) - S(x-1 - (size-i-1))
// x + 2 * S(x-1) - S(x-1-i) - S(x-size+i)
// S(y) = y * (y + 1) / 2
We should minimize the sum for it to be <= maxSum
, so naturally, we place the maximum at index
and do strictly lower the sibling numbers.
Looking at the example, we see there is an arithmetic sum to the left and to the right of the index
.
\(S(n) = 1 + 2 + .. + (n-1) + n = n * (n+1) / 2\)
We are also must subtract part of the sum, that out of the array:
\(\sum = S(x-1) - S(x-1-i) + x + S(x-1) - S(x-1 - (size-i-1))\)
Another catch, numbers can’t be 0
, so we must start with an array filled of 1
: 1 1 1 1 1 1
. That will modify our algorithm, adding n
to the sum
and adding one more step to the max
.
Given that we know sum
for each max
, and sum
will grow with increasing of the max
, we can do a binary search sum = f(max)
for max
.
For more robust binary search:
lo
and hi
lo == hi
max = mid
fun maxValue(n: Int, index: Int, maxSum: Int): Int {
val s: (Int) -> Long = { if (it < 0L) 0L else it.toLong() * (it.toLong() + 1L) / 2L }
var lo = 0
var hi = maxSum
var max = lo
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
val sum = n + mid + 2L * s(mid - 1) - s(mid - 1 - index) - s(mid - n + index)
if (sum <= maxSum) {
max = mid
lo = mid + 1
} else hi = mid - 1
}
return max + 1
}
744. Find Smallest Letter Greater Than Target easy blog post substack
https://t.me/leetcode_daily_unstoppable/240
Lowest char greater than target
.
In a sorted array, we can use the Binary Search.
For more robust code:
lo
and hi
lo == hi
lo
or hi
res = ...
mid
fun nextGreatestLetter(letters: CharArray, target: Char): Char {
var res = letters[0]
var lo = 0
var hi = letters.lastIndex
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (letters[mid] > target) {
hi = mid - 1
res = letters[mid]
} else lo = mid + 1
}
return res
}
1351. Count Negative Numbers in a Sorted Matrix easy blog post substack
https://t.me/leetcode_daily_unstoppable/239
Count negatives in a sorted by row and by column matrix.
Consider example:
4 3 2 -1
3 2 1 -1
1 1 -1 -2
^ we are here
-1 -1 -2 -3
If we set position x
at the first negative number, it is guaranteed, that the next row[x]
will also be negative. So we can skip already passed columns.
Let’s use Kotlin’s fold
operator.
fun countNegatives(grid: Array<IntArray>): Int =
grid.fold(0 to 0) { (total, prev), row ->
var curr = prev
while (curr < row.size && row[row.lastIndex - curr] < 0) curr++
(total + curr) to curr
}.first
}
1318. Minimum Flips to Make a OR b Equal to c medium blog post substack
https://t.me/leetcode_daily_unstoppable/238
Minimum a
and b
Int bit flips to make a or b == c
.
Naive implementation is to iterate over 32
bits and flip a
or/and b
bits to match c
.
If we didn’t consider the case where a = 1
and b = 1
and c = 0
, the result would be (a or b) xor c
, as a or b
gives us the left side of the equation, and xor c
gives only bits that are needed to flip. For the corner case a = b = 1, c = 0
, we must do additional flip to make 0
, and we must make any other combinations 0
:
a b c a and b c.inv() (a and b) and c.inv()
0 0 1 0 0 0
0 1 0 0 1 0
0 1 1 0 0 0
1 0 0 0 1 0
1 0 1 0 0 0
1 1 0 1 1 1
1 1 1 1 0 0
Use Integer.bitCount
.
fun minFlips(a: Int, b: Int, c: Int): Int =
Integer.bitCount((a or b) xor c) + Integer.bitCount((a and b) and c.inv())
1502. Can Make Arithmetic Progression From Sequence easy blog post substack
https://t.me/leetcode_daily_unstoppable/237
Is IntArray
can be arithmetic progression?
Sort, then use sliding window.
Let’s write Kotlin one-liner.
fun canMakeArithmeticProgression(arr: IntArray): Boolean =
arr.sorted().windowed(2).groupBy { it[1] - it[0] }.keys.size == 1
1232. Check If It Is a Straight Line easy blog post substack
https://t.me/leetcode_daily_unstoppable/236
Are all the x,y
points in a line?
We can compare \(tan_i = dy_i/dx_i = dy_0/dx_0\)
fun checkStraightLine(coordinates: Array<IntArray>): Boolean =
with((coordinates[1][1] - coordinates[0][1])/
(coordinates[1][0] - coordinates[0][0]).toDouble()) {
coordinates.drop(2).all {
val o = (it[1] - coordinates[0][1]) / (it[0] - coordinates[0][0]).toDouble()
isInfinite() && o.isInfinite() || this == o
}
}
547. Number of Provinces medium blog post substack
https://t.me/leetcode_daily_unstoppable/235
Count connected groups in graph.
Union-Find will perfectly fit to solve this problem.
For more optimal Union-Find:
root
method: uf[it] = x
a(n)
- reverse Ackerman function f(x) = 2^2^2..^2, x times
. a(Int.MAX_VALUE) = 2^32 = 2^2^5 == 3
fun findCircleNum(isConnected: Array<IntArray>): Int {
val uf = IntArray(isConnected.size) { it }
val sz = IntArray(isConnected.size) { 1 }
var count = uf.size
val root: (Int) -> Int = {
var x = it
while (uf[x] != x) x = uf[x]
uf[it] = x
x
}
val connect: (Int, Int) -> Unit = { a, b ->
val rootA = root(a)
val rootB = root(b)
if (rootA != rootB) {
count--
if (sz[rootA] < sz[rootB]) {
uf[rootB] = rootA
sz[rootA] += sz[rootB]
sz[rootB] = 0
} else {
uf[rootA] = rootB
sz[rootB] += sz[rootA]
sz[rootA] = 0
}
}
}
for (i in 0..sz.lastIndex)
for (j in 0..sz.lastIndex)
if (isConnected[i][j] == 1) connect(i, j)
return count
}
1376. Time Needed to Inform All Employees medium blog post substack
https://t.me/leetcode_daily_unstoppable/234
Total time
from headID
to all nodes in graph.
Total time will be the maximum time from the root of the graph to the lowest node. To find it out, we can use DFS.
Build the graph, then write the DFS.
fun numOfMinutes(n: Int, headID: Int, manager: IntArray, informTime: IntArray): Int {
val fromTo = mutableMapOf<Int, MutableList<Int>>()
(0 until n).forEach { fromTo.getOrPut(manager[it]) { mutableListOf() } += it }
fun dfs(curr: Int): Int {
return informTime[curr] + (fromTo[curr]?.map { dfs(it) }?.max() ?: 0)
}
return dfs(headID)
}
2101. Detonate the Maximum Bombs medium blog post substack
https://t.me/leetcode_daily_unstoppable/233
Count detonated bombs by chain within each radius.
A bomb will only detonate if its center within the radius of another.
For example, A
can detonate B
, but not otherwise.
Let’s build a graph, who’s who can detonate.
Build a graph, the do DFS trying to start from each node.
n
DFS will take \(n^2\)
fun maximumDetonation(bombs: Array<IntArray>): Int {
val fromTo = mutableMapOf<Int, MutableList<Int>>()
for (i in 0..bombs.lastIndex) {
val bomb1 = bombs[i]
val rr = bomb1[2] * bomb1[2].toLong()
val edges = fromTo.getOrPut(i) { mutableListOf() }
for (j in 0..bombs.lastIndex) {
if (i == j) continue
val bomb2 = bombs[j]
val dx = (bomb1[0] - bomb2[0]).toLong()
val dy = (bomb1[1] - bomb2[1]).toLong()
if (dx * dx + dy * dy <= rr) edges += j
}
}
fun dfs(curr: Int, visited: HashSet<Int> = HashSet()): Int {
return if (visited.add(curr)) {
1 + (fromTo[curr]?.sumBy { dfs(it, visited) } ?:0)
} else 0
}
var max = 1
for (i in 0..bombs.lastIndex) max = maxOf(max, dfs(i))
return max
}
1091. Shortest Path in Binary Matrix medium blog post substack
https://t.me/leetcode_daily_unstoppable/232
0
path length in a binary square matrix.
Just do BFS.
Some tricks for cleaner code:
range
dirs
. This is a sequence of x
and y
fun shortestPathBinaryMatrix(grid: Array<IntArray>): Int =
with(ArrayDeque<Pair<Int, Int>>()) {
val range = 0..grid.lastIndex
val dirs = arrayOf(0, 1, 0, -1, -1, 1, 1, -1)
if (grid[0][0] == 0) add(0 to 0)
grid[0][0] = -1
var step = 0
while (isNotEmpty()) {
step++
repeat(size) {
val (x, y) = poll()
if (x == grid.lastIndex && y == grid.lastIndex) return step
var dx = -1
for (dy in dirs) {
val nx = x + dx
val ny = y + dy
if (nx in range && ny in range && grid[ny][nx] == 0) {
grid[ny][nx] = -1
add(nx to ny)
}
dx = dy
}
}
}
-1
}
1396. Design Underground System medium blog post substack
https://t.me/leetcode_daily_unstoppable/229
Average time from, to
when different user IDs do checkIn(from, time1)
and checkOut(to, time2)
Just do what is asked, use HashMap
to track user’s last station.
sum
time and count
for every from, to
stationPair
as key for HashMap
class UndergroundSystem() {
val fromToSumTime = mutableMapOf<Pair<String, String>, Long>()
val fromToCount = mutableMapOf<Pair<String, String>, Int>()
val idFromTime = mutableMapOf<Int, Pair<String, Int>>()
fun Pair<String, String>.time() = fromToSumTime[this] ?: 0L
fun Pair<String, String>.count() = fromToCount[this] ?: 0
fun checkIn(id: Int, stationName: String, t: Int) {
idFromTime[id] = stationName to t
}
fun checkOut(id: Int, stationName: String, t: Int) {
val (from, tFrom) = idFromTime[id]!!
val fromTo = from to stationName
fromToSumTime[fromTo] = t - tFrom + fromTo.time()
fromToCount[fromTo] = 1 + fromTo.count()
}
fun getAverageTime(startStation: String, endStation: String): Double =
with(startStation to endStation) {
time().toDouble() / count().toDouble()
}
}
705. Design HashSet easy blog post substack
https://t.me/leetcode_daily_unstoppable/228
Write a HashSet
.
There are different hash functions. Interesting implementations is In Java HashMap
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/HashMap.java
Use key % size
for the hash function, grow and rehash when needed.
class MyHashSet(val initialSz: Int = 16, val loadFactor: Double = 1.6) {
var buckets = Array<LinkedList<Int>?>(initialSz) { null }
var size = 0
fun hash(key: Int): Int = key % buckets.size
fun rehash() {
if (size > buckets.size * loadFactor) {
val oldBuckets = buckets
buckets = Array<LinkedList<Int>?>(buckets.size * 2) { null }
oldBuckets.forEach { it?.forEach { add(it) } }
}
}
fun bucket(key: Int): LinkedList<Int> {
val hash = hash(key)
if (buckets[hash] == null) buckets[hash] = LinkedList<Int>()
return buckets[hash]!!
}
fun add(key: Int) {
val list = bucket(key)
if (!list.contains(key)) {
list.add(key)
size++
rehash()
}
}
fun remove(key: Int) {
bucket(key).remove(key)
}
fun contains(key: Int): Boolean =
bucket(key).contains(key)
}
1603. Design Parking System easy blog post substack
https://t.me/leetcode_daily_unstoppable/227
Return if car of type 1, 2 or 3
can be added given sizes big, medium and small
.
Just write the code.
Let’s use an array to minimize the number of lines.
class ParkingSystem(big: Int, medium: Int, small: Int) {
val types = arrayOf(big, medium, small)
fun addCar(carType: Int): Boolean = types[carType - 1]-- > 0
}
1547. Minimum Cost to Cut a Stick hard blog post substack
https://t.me/leetcode_daily_unstoppable/226
Min cost of cuts c1,..,ci,..,cn
of [0..n]
where cut cost the length = to-from
.
We every stick from..to
we can try all the cuts in that range. This result will be optimal and can be cached.
k
, and we loop for k
.
fun minCost(n: Int, cuts: IntArray): Int {
val cache = mutableMapOf<Pair<Int, Int>, Int>()
fun dfs(from: Int, to: Int): Int {
return cache.getOrPut(from to to) {
var min = 0
cuts.forEach {
if (it in from + 1..to - 1) {
val new = to - from + dfs(from, it) + dfs(it, to)
if (min == 0 || new < min) min = new
}
}
min
}
}
return dfs(0, n)
}
1406. Stone Game III hard blog post substack
https://t.me/leetcode_daily_unstoppable/225
Winner of “Alice”, “Bob” or “Tie” in game of taking 1, 2 or 3
stones by turn from stoneValue
array.
Let’s count the result for Alice, starting at i
element:
\(alice_i = \sum_{i=1,2,3}(stones_i) + suffix_i - alice_{i+1}\)
The result can be safely cached. Bob’s points will be Alice’s points in the next turn.
Let’s write bottom up DP.
cache
and suffix
arrays for simpler code
fun stoneGameIII(stoneValue: IntArray): String {
val suffix = IntArray(stoneValue.size + 1)
for (i in stoneValue.lastIndex downTo 0) suffix[i] = stoneValue[i] + suffix[i + 1]
val cache = IntArray(stoneValue.size + 1)
var bob = 0
for (curr in stoneValue.lastIndex downTo 0) {
var sum = 0
var first = true
for (j in 0..2) {
val ind = curr + j
if (ind > stoneValue.lastIndex) break
sum += stoneValue[ind]
val nextAlice = cache[ind + 1]
val next = suffix[ind + 1] - nextAlice
if (first || sum + next > cache[curr]) {
first = false
cache[curr] = sum + next
bob = nextAlice
}
}
}
return if (cache[0] == bob) "Tie" else if (cache[0] > bob) "Alice" else "Bob"
}
1140. Stone Game II medium blog post substack
https://t.me/leetcode_daily_unstoppable/224
While Alice and Bob optimally take 1..2*m
numbers from piles
find maximum for Alice.
For each position, we can cache the result for Alice starting from it. Next round, Bob will become Alice and use that cached result, but Alice will use the remaining part: \(bob_i = suffix_i - alice_i\) and \(alice_i = \sum(piles_{1..x<2m}) + (suffix_i - alice_{i + 1})\)
IntArray
HashMap
for simpler code, or Array for faster
fun stoneGameII(piles: IntArray): Int {
// 2 7 9 4 4 M A B
// A 1 1
// A A 2 2,7
// A B B 2 7,9
// A A B B B 3 9,4,4
val sums = IntArray(piles.size)
sums[0] = piles[0]
for (i in 1..piles.lastIndex)
sums[i] = sums[i - 1] + piles[i]
val total = sums[sums.lastIndex]
val cache = mutableMapOf<Pair<Int, Int>, Int>()
fun dfs(m: Int, curr: Int): Int {
return cache.getOrPut(m to curr) {
var res = 0
var pilesSum = 0
for (x in 0 until 2*m) {
if (curr + x > piles.lastIndex) break
pilesSum += piles[curr + x]
val nextOther = dfs(maxOf(m, x + 1), curr + x + 1)
val nextMy = total - sums[curr + x] - nextOther
res = maxOf(res, pilesSum + nextMy)
}
res
}
}
return dfs(1, 0)
}
837. New 21 Game medium blog post substack
https://t.me/leetcode_daily_unstoppable/223
Probability sum of random numbers 1..maxPts
sum be < n
after it overflow k
.
For every event, we choose one
number from numbers 1..maxPts
. Probability of this event is p1 = 1/maxPts
.
For example, n=6, k=1, maxpts=10
: we can pick any numbers 1, 2, 3, 4, 5, 6
that are <=6
. Numbers 7, 8, 9, 10
are excluded, because they are >6
. After we pick one number with probability p1 = 1/10
, the sum will be >=k
so we stop. The final probability is the sum of individual valid choices p = sum(good_p1)
Another example, n=6, k=2, maxpts=10
: our choices are
// n = 6, k = 2, maxpts = 10
// p_win1 1+1, 1+2, 1+3, 1+4, 1+5, 2, 3, 4, 5, 6
// 0.01 0.01 0.01 0.01 0.01 0.1 0.1 0.1 0.1 0.1 = 0.55
When we go to the second round in cases of 1+1, 1+2, 1+3, 1+4, 1+5
, we multiply the probabilities, so p(1+1) = p1*p1
.
Next, observe the pattern for other examples:
// n = 6, k = 3, maxpts = 10
// p_win 1+1+1, 1+1+2, 1+1+3, 1+1+4, 1+2, 1+3, 1+4, 1+5, 2+1, 2+2, 2+3, 2+4, 3, 4, 5, 6
// 0.001 0.001 0.001 0.001 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.1 0.1 0.1 0.1
// sum=0.484
// n = 6, k = 4, maxpts = 10
// p_win 1+1+1+1 1+1+1+2 1+1+1+3 1+1+2 1+1+3 1+1+4 1+2+1 1+2+2 1+2+3 1+3 1+4 1+5 2+1+1 2+1+2 2+1+3 2+2 2+3 2+4 3+1 3+2 3+3 4 5 6
// .0001 .0001 .0001 .001 .001 .001 .001 .001 .001 .01 .01 .01 .001 .001 .001 .01 .01 .01 .01 .01 .01 .1 .1 .1
//sum=0.3993
What we see is the sequence of 1+1+1+1, 1+1+1+2, 1+1+1+3
, where we pick a number from 1..maxpts
then calculate the sum and if the sum is still smaller than n
we go deeper and make another choice from 1..maxpts
.
That can be written as Depth-First Search algorithm:
fun dfs(currSum: Int): Double {
...
var sumP = 0.0
for (x in 1..maxPts)
sumP += dfs(currSum + x)
res = sumP * p1
}
This will work and gives us correct answers, but gives TLE for big numbers, as its time complexity is \(O(n^2)\).
Let’s observe this algorithm’s recurrent equation:
\(f(x) = (f(x+1) + f(x+2)+..+f(x + maxPts))*p1\)
\(f(x + 1) = (f(x+2) + f(x+3) +...+f(x + maxPts)*p1 + f(x + 1 + maxPts))*p1\)
subtract second sequence from the first:
\(f(x) - f(x + 1) = f(x+1)*p1 - f(x+1+maxPts)*p1\)
\(f(x) = f(x+1) + (f(x+1) - f(x+1+maxPts))*p1\)
This removes one dimension of iteration 1..maxPts
. However, it fails the first case where currSum == k - 1
, because the equation didn’t consider that not all x+maxPts
are smaller than n
. For this case, we must return (n-k+1)*p1
as we choose last number from range k - 1..n
.
This problem is next to impossible to solve in an interview, given how many conclusions you must derive on the fly. So, hope no one will give it to you.
fun new21Game(n: Int, k: Int, maxPts: Int): Double {
// n = 6, k = 1, maxpts = 10
// cards: 1 2 3 4 5 6 7 8 9 10
// p_win1(6, 10) = count(1 2 3 4 5 6) / 10 = 0.6
// n = 6, k = 2, maxpts = 10
// p_win1 1+1, 1+2, 1+3, 1+4, 1+5, 2, 3, 4, 5, 6
// 0.01 0.01 0.01 0.01 0.01 0.1 0.1 0.1 0.1 0.1 = 0.55
// n = 6, k = 3, maxpts = 10
// p_win 1+1+1, 1+1+2, 1+1+3, 1+1+4, 1+2, 1+3, 1+4, 1+5, 2+1, 2+2, 2+3, 2+4, 3, 4, 5, 6
// 0.001 0.001 0.001 0.001 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.1 0.1 0.1 0.1
// sum=0.484
// n = 6, k = 4, maxpts = 10
// p_win 1+1+1+1 1+1+1+2 1+1+1+3 1+1+2 1+1+3 1+1+4 1+2+1 1+2+2 1+2+3 1+3 1+4 1+5 2+1+1 2+1+2 2+1+3 2+2 2+3 2+4 3+1 3+2 3+3 4 5 6
// .0001 .0001 .0001 .001 .001 .001 .001 .001 .001 .01 .01 .01 .001 .001 .001 .01 .01 .01 .01 .01 .01 .1 .1 .1
//sum=0.3993
val p1 = 1.0 / maxPts.toDouble()
val cache = Array<Double>(n + 1) { -1.0 }
// f(x) = (f(x+1) + f(x+2)+..+f(x + maxPts))*p1
// f(x + 1) = (f(x+2) + f(x+3) +...+f(x + maxPts)*p1 + f(x + 1 + maxPts))*p1
// f(x) - f(x + 1) = f(x+1)*p1 - f(x+1+maxPts)*p1
// f(x) = f(x+1) + (f(x+1) - f(x+1+maxPts))*p1
fun dfs(currSum: Int): Double {
if (currSum == k - 1) return minOf(1.0, (n-k+1)*p1) // corner case
if (currSum >= k) return if (currSum <= n) 1.0 else 0.0
if (cache[currSum] != -1.0) return cache[currSum]
//var sumP = 0.0
//for (x in 1..minOf(maxPts, n - currSum)) {
// sumP += dfs(currSum + x)
//}
//val res = sumP * p1
val res = dfs(currSum + 1) + (dfs(currSum + 1) - dfs(currSum + 1 + maxPts)) * p1
cache[currSum] = res
return res
}
return dfs(0)
}
2542. Maximum Subsequence Score medium blog post substack
https://t.me/leetcode_daily_unstoppable/222
Max score of k
sum(subsequence(a)) * min(subsequence(b))
First, the result is independent of the order, so we can sort. For maximum score, it better to start with maximum multiplier of min
. Then, we iterate from biggest nums2 to smallest. Greedily add numbers until we reach k
elements. After size > k
, we must consider what element to extract. Given our min
is always the current value, we can safely take any element without modifying the minimum, thus take out the smallest by nums1
.
PriorityQueue
to dynamically take out the smallestsize == k
, as it may decrease with more elements
fun maxScore(nums1: IntArray, nums2: IntArray, k: Int): Long {
// 14 2 1 12 100000000000 1000000000000 100000000000
// 13 11 7 1 1 1 1
val inds = nums1.indices.sortedWith(
compareByDescending<Int> { nums2[it] }
.thenByDescending { nums1[it] })
var score = 0L
var sum = 0L
val pq = PriorityQueue<Int>(compareBy { nums1[it] })
inds.forEach {
sum += nums1[it].toLong()
pq.add(it)
if (pq.size > k) sum -= nums1[pq.poll()].toLong()
if (pq.size == k) score = maxOf(score, sum * nums2[it].toLong())
}
return score
}
703. Kth Largest Element in a Stream medium blog post substack
https://t.me/leetcode_daily_unstoppable/221
Kth largest
We need to keep all values smaller than current largest kth element and can safely drop all other elements.
Use PriorityQueue
.
class KthLargest(val k: Int, nums: IntArray) {
val pq = PriorityQueue<Int>(nums.toList())
fun add(v: Int): Int = with (pq) {
add(v)
while (size > k) poll()
peek()
}
}
347. Top K Frequent Elements medium blog post substack
https://t.me/leetcode_daily_unstoppable/220
First k
unique elements sorted by frequency.
Group by frequency 1 1 1 5 5 -> 1:3, 5:2
, then bucket sort frequencies 2:5, 3:1
, then flatten and take first k
.
fun topKFrequent(nums: IntArray, k: Int): IntArray {
val freq = nums.groupBy { it }.mapValues { it.value.size }
val freqToNum = Array<MutableList<Int>>(nums.size + 1) { mutableListOf() }
freq.forEach { (num, fr) -> freqToNum[nums.size + 1 - fr].add(num) }
return freqToNum
.filter { it.isNotEmpty() }
.flatten()
.take(k)
.toIntArray()
}
934. Shortest Bridge medium blog post substack
https://t.me/leetcode_daily_unstoppable/219
Find the shortest path from one island of 1
’s to another.
Shortest path can be found with Breadth-First Search if we start it from every border cell of the one of the islands. To detect border cell, we have to make separate DFS.
visited
set
fun Array<IntArray>.inRange(xy: Pair<Int, Int>) = (0..lastIndex).let {
xy.first in it && xy.second in it
}
fun Pair<Int, Int>.siblings() = arrayOf(
(first - 1) to second, first to (second - 1),
(first + 1) to second, first to (second + 1)
)
fun shortestBridge(grid: Array<IntArray>): Int {
val queue = ArrayDeque<Pair<Int, Int>>()
fun dfs(x: Int, y: Int) {
if (grid[y][x] == 1) {
grid[y][x] = 2
(x to y).siblings().filter { grid.inRange(it) }.forEach { dfs(it.first, it.second) }
} else if (grid[y][x] == 0) queue.add(x to y)
}
(0 until grid.size * grid.size)
.map { it / grid.size to it % grid.size}
.filter { (y, x) -> grid[y][x] == 1 }
.first().let { (y, x) -> dfs(x, y)}
return with (queue) {
var steps = 1
while (isNotEmpty()) {
repeat(size) {
val xy = poll()
if (grid.inRange(xy)) {
val (x, y) = xy
if (grid[y][x] == 1) return@shortestBridge steps - 1
if (grid[y][x] == 0) {
grid[y][x] = 3
addAll(xy.siblings().filter { grid.inRange(it) })
}
}
}
steps++
}
-1
}
}
399. Evaluate Division medium blog post substack
https://t.me/leetcode_daily_unstoppable/218
Given values for a/b
and b/c
find answers for a/c
.
Let’s build a graph, a
-> b
with weights of values[a/b]
. Then answer is a path from one node to the other. The shortest path can be found with a Breadth-First Search.
x/x
, where x
is not in a graph.
fun calcEquation(equations: List<List<String>>, values: DoubleArray, queries: List<List<String>>): DoubleArray {
val fromTo = mutableMapOf<String, MutableList<Pair<String, Double>>>()
equations.forEachIndexed { i, (from, to) ->
fromTo.getOrPut(from) { mutableListOf() } += to to values[i]
fromTo.getOrPut(to) { mutableListOf() } += from to (1.0 / values[i])
}
// a/c = a/b * b/c
return queries.map { (from, to) ->
with(ArrayDeque<Pair<String, Double>>()) {
val visited = HashSet<String>()
visited.add(from)
if (fromTo.containsKey(to)) add(from to 1.0)
while (isNotEmpty()) {
repeat(size) {
val (point, value) = poll()
if (point == to) return@map value
fromTo[point]?.forEach { (next, nvalue) ->
if (visited.add(next)) add(next to value * nvalue)
}
}
}
-1.0
}
}.toDoubleArray()
}
785. Is Graph Bipartite? medium blog post substack
https://t.me/leetcode_daily_unstoppable/217
Find if graph is bipartite
Mark edge Red
or Blue
and it’s nodes in the opposite.
vertices
and edges
reds
and visited
set.
fun isBipartite(graph: Array<IntArray>): Boolean {
val reds = IntArray(graph.size)
fun dfs(u: Int, isRed: Int): Boolean {
if (reds[u] == 0) {
reds[u] = if (isRed == 0) 1 else isRed
return graph[u].all { dfs(it, -reds[u]) }
} else return reds[u] == isRed
}
return graph.indices.all { dfs(it, reds[it]) }
}
1557. Minimum Number of Vertices to Reach All Nodes medium blog post substack
https://t.me/leetcode_daily_unstoppable/216
Find all starting nodes in graph.
Count nodes that have no incoming connections.
fun findSmallestSetOfVertices(n: Int, edges: List<List<Int>>): List<Int> =
(0 until n) - edges.map { it[1] }
2130. Maximum Twin Sum of a Linked List medium blog post substack
https://t.me/leetcode_daily_unstoppable/215
Max sum of head-tail twin ListNodes: a-b-c-d -> max(a+d, b+c)
Add first half to the Stack
, then pop until end reached.
fast
and slow
pointers to find the center.
fun pairSum(head: ListNode?): Int {
var fast = head
var slow = head
var sum = 0
val stack = Stack<Int>()
while (fast != null) {
stack.add(slow!!.`val`)
slow = slow.next
fast = fast.next?.next
}
while (slow != null) {
sum = maxOf(sum, stack.pop() + slow.`val`)
slow = slow.next
}
return sum
}
24. Swap Nodes in Pairs medium blog post substack
https://t.me/leetcode_daily_unstoppable/214
Swap adjacent ListNodes a-b-c-d -> b-a-d-c
.
Those kinds of problems are easy, but your task is to write it bug free from the first go.
For more robust code:
dummy
head to track for a new head
fun swapPairs(head: ListNode?): ListNode? {
val dummy = ListNode(0).apply { next = head }
var curr: ListNode? = dummy
while (curr?.next != null && curr?.next?.next != null) {
// curr->one->two->next
// curr->two->one->next
var one = curr.next
var two = one?.next
val next = two?.next
curr.next = two
two?.next = one
one?.next = next
curr = one
}
return dummy.next
}
1721. Swapping Nodes in a Linked List medium blog post substack
https://t.me/leetcode_daily_unstoppable/213
Swap the values of the head-tail k’th ListNodes.
As we aren’t asked to swap nodes, the problem is to find nodes.
Travel the fast
pointer at k
distance, then move both fast
and two
nodes until fast
reaches the end.
fun swapNodes(head: ListNode?, k: Int): ListNode? {
var fast = head
for (i in 1..k - 1) fast = fast?.next
val one = fast
var two = head
while (fast?.next != null) {
two = two?.next
fast = fast?.next
}
one?.`val` = two?.`val`.also { two?.`val` = one?.`val` }
return head
}
1799. Maximize Score After N Operations hard blog post substack
https://t.me/leetcode_daily_unstoppable/212
Max indexed-gcd-pair sum from 2n array; [3,4,6,8] -> 11 (1gcd(3,6) + 2gcd(4,8))
For each step
and remaining items, the result is always the same, so is memorizable.
bitmask
to avoid double counting
fun gcd(a: Int, b: Int): Int = if (b % a == 0) a else gcd(b % a, a)
fun maxScore(nums: IntArray): Int {
val n = nums.size / 2
val cache = Array(n + 1) { IntArray(1 shl nums.size) { -1 } }
fun dfs(step: Int, mask: Int): Int {
if (step > n) return 0
if (cache[step][mask] != -1) return cache[step][mask]
var max = 0
for (i in 0..nums.lastIndex) {
val ibit = 1 shl i
if (mask and ibit != 0) continue
for (j in (i + 1)..nums.lastIndex) {
val jbit = 1 shl j
if (mask and jbit != 0) continue
val curr = step * gcd(nums[i], nums[j])
val next = dfs(step + 1, mask or ibit or jbit)
max = maxOf(max, curr + next)
}
}
cache[step][mask] = max
return max
}
return dfs(1, 0)
}
2466. Count Ways To Build Good Strings medium blog post substack
https://t.me/leetcode_daily_unstoppable/211
Count distinct strings, length low to high, appending ‘0’ zero or ‘1’ one times. Return count % 1,000,000,007.
Let’s add zero
’s or one
’s one by one. For each current length, the resulting count is independent of all the previous additions. We can cache the result by the current size
of the string.
Let’s write a DFS solution, adding zero
or one
and count the good strings.
Then we can rewrite it to the iterative DP.
top-down:
fun countGoodStrings(low: Int, high: Int, zero: Int, one: Int): Int {
val m = 1_000_000_007
val cache = mutableMapOf<Int, Int>()
fun dfs(currSize: Int): Int {
if (currSize > high) return 0
return cache.getOrPut(currSize) {
val curr = if (currSize in low..high) 1 else 0
val addZeros = if (zero > 0) dfs(currSize + zero) else 0
val addOnes = if (one > 0) dfs(currSize + one) else 0
(curr + addZeros + addOnes) % m
}
}
return dfs(0)
}
bottom-up
fun countGoodStrings(low: Int, high: Int, zero: Int, one: Int): Int {
val cache = mutableMapOf<Int, Int>()
for (sz in high downTo 0)
cache[sz] = ((if (sz >= low) 1 else 0)
+ (cache[sz + zero]?:0)
+ (cache[sz + one]?:0)) % 1_000_000_007
return cache[0]!!
}
2140. Solving Questions With Brainpower medium
fun mostPoints(questions: Array<IntArray>): Long {
val dp = LongArray(questions.size)
for (i in questions.lastIndex downTo 0) {
val (points, skip) = questions[i]
val tail = if (i + skip + 1 > questions.lastIndex) 0 else dp[i + skip + 1]
val notTake = if (i + 1 > questions.lastIndex) 0 else dp[i + 1]
dp[i] = maxOf(points + tail, notTake)
}
return dp[0]
}
or minified golf version
fun mostPoints(questions: Array<IntArray>): Long {
val dp = HashMap<Int, Long>()
for ((i, q) in questions.withIndex().reversed())
dp[i] = maxOf(q[0] + (dp[i + q[1] + 1]?:0), dp[i + 1]?:0)
return dp[0]?:0
}
https://t.me/leetcode_daily_unstoppable/210
If we go from the tail, for each element we are interested only on what happens to the right
from it. Prefix of the array is irrelevant, when we’re starting from the element i
, because we sure know, that we are taking it and not skipping.
Given that, dynamic programming equation is:
\(dp_i = max(points_i + dp_{i+1+skip_i}, dp_{i+1})\), where dp
is the mostPoints
starting from position i
.
Let’s implement a bottom-up solution.
1035. Uncrossed Lines medium
fun maxUncrossedLines(nums1: IntArray, nums2: IntArray): Int {
val cache = Array(nums1.size) { Array(nums2.size) { -1 } }
val intersect = nums1.toSet().intersect(nums2.toSet())
fun dfs(i: Int, j: Int, x: Int): Int {
if (i == nums1.size || j == nums2.size) return 0
val cached = cache[i][j]
if (cached != -1) return cached
val n1 = nums1[i]
val n2 = nums2[j]
val drawLine = if (n1 == x && n2 == x || n1 == n2) 1 + dfs(i + 1, j + 1, n1) else 0
val skipTop = dfs(i + 1, j, x)
val skipBottom = dfs(i, j + 1, x)
val skipBoth = dfs(i + 1, j + 1, x)
val startTop = if (intersect.contains(n1)) dfs(i + 1, j, n1) else 0
val startBottom = if (intersect.contains(n2)) dfs(i, j + 1, n2) else 0
val res = maxOf(
drawLine,
maxOf(drawLine, skipTop, skipBottom),
maxOf(skipBoth, startTop, startBottom)
)
cache[i][j] = res
return res
}
return dfs(0, 0, 0)
}
https://t.me/leetcode_daily_unstoppable/209
Consider the case:
2 5 1 2 5
2 2 2 1 1 1 5 5 5
When we draw all the possible lines, we see that there is a choice to draw line 2-2
or four lines 1-1
or three 5-5
in the middle. Suffix lines 5-5
and prefix lines 2-2
are optimal already and can be cached as a result.
To find an optimal choice we can use DFS.
We can prune some impossible combinations by precomputing the intersected numbers and considering them only.
intersect
method in Kotlin59. Spiral Matrix II medium
fun generateMatrix(n: Int): Array<IntArray> = Array(n) { IntArray(n) }.apply {
var dir = 0
var dxdy = arrayOf(0, 1, 0, -1)
var x = 0
var y = 0
val nextX = { x + dxdy[(dir + 1) % 4] }
val nextY = { y + dxdy[dir] }
val valid = { x: Int, y: Int -> x in 0..n-1 && y in 0..n-1 && this[y][x] == 0 }
repeat (n * n) {
this[y][x] = it + 1
if (!valid(nextX(), nextY())) dir = (dir + 1) % 4
x = nextX()
y = nextY()
}
}
https://t.me/leetcode_daily_unstoppable/208
Just implement what is asked. Let’s have the strategy of a robot: move it in one direction until it hits a wall, then change the direction.
== 0
54. Spiral Matrix medium
fun spiralOrder(matrix: Array<IntArray>): List<Int> = mutableListOf<Int>().apply {
var x = 0
var y = 0
val dxy = arrayOf(0, 1, 0, -1)
val borders = arrayOf(matrix[0].lastIndex, matrix.lastIndex, 0, 0)
var dir = 0
val moveBorder = { border: Int -> borders[border] += if (border < 2) -1 else 1 }
repeat (matrix.size * matrix[0].size) {
if ((if (dir % 2 == 0) x else y) == borders[dir]) {
moveBorder((dir + 3) % 4)
dir = (dir + 1) % 4
}
add(matrix[y][x])
x += dxy[(dir + 1) % 4]
y += dxy[dir]
}
}
https://t.me/leetcode_daily_unstoppable/207
Just implement what is asked.
We can use a loop with four directions, or try to program a robot
that will rotate after it hit a wall.
left
, top
, right
, bottom
dir
1572. Matrix Diagonal Sum easy
fun diagonalSum(mat: Array<IntArray>): Int =
(0..mat.lastIndex).sumBy {
mat[it][it] + mat[it][mat.lastIndex - it]
}!! - if (mat.size % 2 == 0) 0 else mat[mat.size / 2][mat.size / 2]
https://t.me/leetcode_daily_unstoppable/206
Just do what is asked.
1964. Find the Longest Valid Obstacle Course at Each Position hard
fun longestObstacleCourseAtEachPosition(obstacles: IntArray): IntArray {
// 2 3 1 3
// 2 2
// 3 2 3
// 1 1 3 (pos = 1)
// 3 1 3 3
// 5 2 5 4 1 1 1 5 3 1
// 5 . 5
// 2 . 2
// 5 . 2 5
// 4 . 2 4
// 1 1 4 (pos = 1)
// 1 1 1
// 1 1 1 1
// 5 1 1 1 5
// 3 1 1 1 3
// 1 1 1 1 1
val lis = IntArray(obstacles.size)
var end = 0
return obstacles.map { x ->
var pos = -1
var lo = 0
var hi = end - 1
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (lis[mid] > x) {
hi = mid - 1
pos = mid
} else lo = mid + 1
}
if (pos == -1) {
lis[end++] = x
end
} else {
lis[pos] = x
pos + 1
}
}.toIntArray()
}
https://t.me/leetcode_daily_unstoppable/205
This is the Longest Increasing Subsequence length problem, that have a classic algorithm, which must be learned and understood.
The trivial case of any increasing subsequence
is broken by example: 2 3 1 3
, when we consider the last 3
result must be: 233
instead of 13
. So, we must track all the sequences.
To track all the sequences, we can use TreeMap
that will hold the largest
element and length of any subsequence. Adding a new element will take \(O(n^2)\).
The optimal LIS
solution is to keep the largest increasing subsequence so far and cleverly add new elements:
smallest
element that is larger
than itLIS
lis
1498. Number of Subsequences That Satisfy the Given Sum Condition medium
fun numSubseq(nums: IntArray, target: Int): Int {
val m = 1_000_000_007
nums.sort()
val cache = IntArray(nums.size + 1) { 0 }
cache[1] = 1
for (i in 2..nums.size) cache[i] = (2 * cache[i - 1]) % m
var total = 0
nums.forEachIndexed { i, n ->
var lo = 0
var hi = i
var removed = cache[i + 1]
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (nums[mid] + n <= target) {
removed = cache[i - mid]
lo = mid + 1
} else hi = mid - 1
}
total = (total + cache[i + 1] - removed) % m
}
if (total < 0) total += m
return total
}
https://t.me/leetcode_daily_unstoppable/204
max
or min
in a subsequence.target
, each new number adds previous value to the sum: \(sum_2 = sum_1 + (1 + sum_1)\), or just \(2^i\).target = 12
, for number 8
, count of excluded values is 4
= [568, 58, 68, 8]; for number 9
, it is 8
= [5689, 589, 569, 59, 689, 69, 89, 9]. We can observe, it is determined by the sequence 5 6 8 9
, where all the numbers are bigger, than target - 9
. That is, the law for excluding the elements is the same: \(r_2 = r_1 + (1 + r_1)\), or just \(2^x\), where x - is the count of the bigger numbers.n_i + x <= target
1_000_000_7
1456. Maximum Number of Vowels in a Substring of Given Length medium
fun maxVowels(s: String, k: Int): Int {
val vowels = setOf('a', 'e', 'i', 'o', 'u')
var count = 0
var max = 0
for (i in 0..s.lastIndex) {
if (s[i] in vowels) count++
if (i >= k && s[i - k] in vowels) count--
if (count > max) max = count
}
return max
}
https://t.me/leetcode_daily_unstoppable/203
Count vowels, increasing them on the right border and decreasing on the left of the sliding window.
Set
to check if it is a vowela[i - k]
to detect if we must start move left border from i == k
649. Dota2 Senate medium
fun predictPartyVictory(senate: String): String {
val queue = ArrayDeque<Char>()
senate.forEach { queue.add(it) }
var banR = 0
var banD = 0
while (true) {
var haveR = false
var haveD = false
repeat(queue.size) {
val c = queue.poll()
if (c == 'R') {
haveR = true
if (banR > 0) banR--
else {
queue.add(c)
banD++
}
} else {
haveD = true
if (banD > 0) banD--
else {
queue.add(c)
banR++
}
}
}
if (!haveR) return "Dire"
if (!haveD) return "Radiant"
}
}
https://t.me/leetcode_daily_unstoppable/202
One can ban on any length to the right. We can just simulate the process, and it will take at most two rounds.
Use Queue
and count how many bans are from the Radiant and from the Dire.
2215. Find the Difference of Two Arrays easy
fun findDifference(nums1: IntArray, nums2: IntArray): List<List<Int>> = listOf(
nums1.subtract(nums2.toSet()).toList(),
nums2.subtract(nums1.toSet()).toList()
)
https://t.me/leetcode_daily_unstoppable/201
Just do what is asked.
One way is to use two Sets
and just filter them.
Another is to use intersect
and distinct
.
Third option is to sort both of them and iterate, that will use \(O(1)\) extra memory, but \(O(nlogn)\) time.
1822. Sign of the Product of an Array easy
fun arraySign(nums: IntArray): Int = nums.fold(1) { r, t -> if (t == 0) 0 else r * (t / Math.abs(t)) }
https://t.me/leetcode_daily_unstoppable/199
Do what is asked, but avoid overflow.
There is an sign
function in kotlin, but leetcode.com doesn’t support it yet.
We can use fold
.
1491. Average Salary Excluding the Minimum and Maximum Salary easy
fun average(salary: IntArray): Double = with (salary) {
(sum() - max()!! - min()!!) / (size - 2).toDouble()
}
or
fun average(salary: IntArray): Double = salary.sorted().drop(1).dropLast(1).average()
https://t.me/leetcode_daily_unstoppable/198
Just do what is asked.
We can do .fold
and iterate only once, but sum
, max
and min
operators are less verbose.
We also can sort it, that will make code even shorter.
1579. Remove Max Number of Edges to Keep Graph Fully Traversable hard
fun IntArray.root(a: Int): Int {
var x = a
while (this[x] != x) x = this[x]
this[a] = x
return x
}
fun IntArray.union(a: Int, b: Int): Boolean {
val rootA = root(a)
val rootB = root(b)
if (rootA != rootB) this[rootB] = rootA
return rootA != rootB
}
fun IntArray.connected(a: Int, b: Int) = root(a) == root(b)
fun maxNumEdgesToRemove(n: Int, edges: Array<IntArray>): Int {
val uf1 = IntArray(n + 1) { it }
val uf2 = IntArray(n + 1) { it }
var skipped = 0
edges.forEach { (type, a, b) ->
if (type == 3) {
uf1.union(a, b)
if (!uf2.union(a, b)) skipped++
}
}
edges.forEach { (type, a, b) ->
if (type == 1 && !uf1.union(a, b)) skipped++
}
edges.forEach { (type, a, b) ->
if (type == 2 && !uf2.union(a, b)) skipped++
}
for (i in 2..n)
if (!uf1.connected(i - 1, i) || !uf2.connected(i - 1, i)) return -1
return skipped
}
https://t.me/leetcode_daily_unstoppable/196
After connecting all type 3
nodes, we can skip already connected nodes for Alice and for Bob. To detect if all the nodes are connected, we can just check if all nodes connected to one particular node.
Use separate Union-Find
objects for Alice and for Bob
root
and union
operations take < 5
for any n <= Int.MAX
.1697. Checking Existence of Edge Length Limited Paths hard
fun distanceLimitedPathsExist(n: Int, edgeList: Array<IntArray>, queries: Array<IntArray>): BooleanArray {
val uf = IntArray(n) { it }
fun root(x: Int): Int {
var n = x
while (uf[n] != n) n = uf[n]
uf[x] = n
return n
}
fun union(a: Int, b: Int) {
val rootA = root(a)
val rootB = root(b)
if (rootA != rootB) uf[rootB] = rootA
}
val indices = queries.indices.sortedWith(compareBy( { queries[it][2] } ))
edgeList.sortWith(compareBy( { it[2] } ))
var edgePos = 0
val res = BooleanArray(queries.size)
indices.forEach { ind ->
val (qfrom, qto, maxDist) = queries[ind]
while (edgePos < edgeList.size) {
val (from, to, dist) = edgeList[edgePos]
if (dist >= maxDist) break
union(from, to)
edgePos++
}
res[ind] = root(qfrom) == root(qto)
}
return res
}
https://t.me/leetcode_daily_unstoppable/195
The naive approach is to do BFS for each query, obviously gives TLE as it takes \(O(n^2)\) time.
Using the hint, we can use somehow the sorted order of the queries. If we connect every two nodes with dist < query.dist
we have connected groups with all nodes reachable inside them. The best data structure for union and finding connected groups is the Union-Find.
To avoid iterating edgeList
every time, we can sort it too and take only available distances.
uf[x] = n
edgePos
- a position in a sorted edgeList
indices
list to sort queries without losing the order
root
and union
operations is an inverse Ackerman function and < 5
for every possible number in Int.839. Similar String Groups hard
fun numSimilarGroups(strs: Array<String>): Int {
fun similar(i: Int, j: Int): Boolean {
var from = 0
while (from < strs[i].length && strs[i][from] == strs[j][from]) from++
var to = strs[i].lastIndex
while (to >= 0 && strs[i][to] == strs[j][to]) to--
for (x in from + 1..to - 1)
if (strs[i][x] != strs[j][x]) return false
return true
}
val uf = IntArray(strs.size) { it }
fun root(x: Int): Int {
var n = x
while (uf[n] != n) n = uf[n]
uf[x] = n
return n
}
var groups = strs.size
fun union(a: Int, b: Int) {
val rootA = root(a)
val rootB = root(b)
if (rootA != rootB) {
groups--
uf[rootB] = rootA
}
}
for (i in 0..strs.lastIndex)
for (j in i + 1..strs.lastIndex)
if (similar(i, j)) union(i, j)
return groups
}
https://t.me/leetcode_daily_unstoppable/194
For tracking the groups, Union-Find is a good start. Next, we need to compare the similarity of each to each word, that is \(O(n^2)\).
For the similarity, we need a linear algorithm. Let’s divide the words into three parts: prefix+a+body+b+suffix
. Two words are similar if their prefix
, suffix
and body
are similar, leaving the only different letters a
and b
.
uf[x] = n
ranks
give the optimal time of \(O(lg*n)\), where lg*n
is the inverse Ackerman function. It is inverse of the f(n) = 2^2^2^2..n times.
319. Bulb Switcher medium
fun bulbSwitch(n: Int): Int {
if (n <= 1) return n
var count = 1
var interval = 3
var x = 1
while (x + interval <= n) {
x = x + interval
interval += 2
count++
}
return count
}
https://t.me/leetcode_daily_unstoppable/193
Let’s draw a diagram and see if any pattern here:
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
//
// 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
// 2 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 . 0 .
// 3 0 . . 1 . . 0 . . 1 . . 0 . . 1 .
// 4 1 . . . 1 . . . 0 . . . 1 . . .
// 5 0 . . . . 1 . . . . 1 . . . .
// 6 0 . . . . . 1 . . . . . 0 .
// 7 0 . . . . . . 1 . . . . .
// 8 0 . . . . . . . 0 . . .
// 9 1 . . . . . . . . 1 .
// 10 0 . . . . . . . . .
// 11 0 . . . . . . . .
// 12 0 . . . . . . .
// 13 0 . . . . . .
// 14 0 . . . . .
// 15 0 . . . .
// 16 1 . . .
// 17 0 . .
// 18 0 .
// 19 0
One rule is: number of switches for each new value is a number of divisors.
Another rule: we can reuse the previous result.
However, those rules didn’t help much, let’s observe another pattern: diagonal sequence have increasing intervals of zeros by 2
Use found law and write the code.
k
is:
\(O(k) = O(\sqrt{n})\), which is our time complexity.258. Add Digits easy
fun addDigits(num: Int): Int = if (num == 0) 0 else 1 + ((num - 1) % 9)
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
// 0 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2
// 0 [1..9] [1..9] [1..9] ...
https://t.me/leetcode_daily_unstoppable/192
Observing the pattern:
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
// 0 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2
// 0 [1..9] [1..9] [1..9] ...
There is a repeating part of it: [1..9]
, so we can derive the formula.
It is just an array pointer loop shifted by 1.
2336. Smallest Number in Infinite Set medium
class SmallestInfiniteSet() {
val links = IntArray(1001) { it + 1 }
fun popSmallest(): Int {
val smallest = links[0]
val next = links[smallest]
links[smallest] = 0
links[0] = next
return smallest
}
fun addBack(num: Int) {
if (links[num] == 0) {
var maxLink = 0
while (links[maxLink] <= num) maxLink = links[maxLink]
val next = links[maxLink]
links[maxLink] = num
links[num] = next
}
}
}
https://t.me/leetcode_daily_unstoppable/191
Given the constraints, we can hold every element as a link node to another in an Array. This will give us \(O(1)\) time for pop
operation, but \(O(n)\) for addBack
in the worst case.
A more asymptotically optimal solution, is to use a TreeSet
and a single pointer to the largest popped element.
Let’s implement a sparse array.
pop
\(O(n)\) - constructor and addBack
fun lastStoneWeight(stones: IntArray): Int =
with(PriorityQueue<Int>(compareByDescending { it } )) {
stones.forEach { add(it) }
while (size > 1) add(poll() - poll())
if (isEmpty()) 0 else peek()
}
https://t.me/leetcode_daily_unstoppable/190
Just run the simulation.
PriorityQueue
with compareByDescending
fun numberOfArrays(s: String, k: Int): Int {
// 131,7 k=1000
// 1317 > 1000
// 20001 k=2000
// 2 count=1
// 000 count=1, curr=2000
// 1 count++, curr=1
//
// 220001 k=2000
// 2 count=1 curr=1
// 22 count+1=2 curr=22 [2, 2], [22]
// 220 curr=220 [2, 20], [220]
// 2200 curr=2200 > 2000, curr=200 [2, 200], [2200]
// 22000 curr=2000 count=1 [2, 2000]
// 220001 count+1=3 curr=20001 > 2000, curr=1 [2, 2000, 1], []
val m = 1_000_000_007L
val cache = LongArray(s.length) { -1L }
fun dfs(curr: Int): Long {
if (curr == s.length) return 1L
if (s[curr] == '0') return 0L
if (cache[curr] != -1L) return cache[curr]
var count = 0L
var num = 0L
for (i in curr..s.lastIndex) {
val d = s[i].toLong() - '0'.toLong()
num = num * 10L + d
if (num > k) break
val countOther = dfs(i + 1)
count = (count + countOther) % m
}
cache[curr] = count
return count
}
return dfs(0).toInt()
}
or bottom-up
fun numberOfArrays(s: String, k: Int): Int {
val cache = LongArray(s.length)
for (curr in s.lastIndex downTo 0) {
if (s[curr] == '0') continue
var count = 0L
var num = 0L
for (i in curr..s.lastIndex) {
num = num * 10L + s[i].toLong() - '0'.toLong()
if (num > k) break
val next = if (i == s.lastIndex) 1 else cache[i + 1]
count = (count + next) % 1_000_000_007L
}
cache[curr] = count
}
return cache[0].toInt()
}
memory optimization:
fun numberOfArrays(s: String, k: Int): Int {
val cache = LongArray(k.toString().length + 1)
for (curr in s.lastIndex downTo 0) {
System.arraycopy(cache, 0, cache, 1, cache.size - 1)
if (s[curr] == '0') {
cache[0] = 0
continue
}
var count = 0L
var num = 0L
for (i in curr..s.lastIndex) {
num = num * 10L + s[i].toLong() - '0'.toLong()
if (num > k) break
val next = if (i == s.lastIndex) 1 else cache[i - curr + 1]
count = (count + next) % 1_000_000_007L
}
cache[0] = count
}
return cache[0].toInt()
}
https://t.me/leetcode_daily_unstoppable/189
One naive solution, is to find all the possible ways of splitting the string, and calculating soFar
number, gives TLE as we must take soFar
into consideration when memoizing the result.
Let’s consider, that for every position in s
there is only one number of possible arrays. Given that, we can start from each position and try to take the first
number in all possible correct ways, such that num < k
. Now, we can cache this result for reuse.
Long
to avoid overflow1312. Minimum Insertion Steps to Make a String Palindrome hard
fun minInsertions(s: String): Int {
// abb -> abba
// abb*
// ab -> aba / bab
// *ab
// bba -> abba
// *bba
// bbbaa -> aabbbaa
// **bbbaa
// bbbcaa -> aacbbbcaa
// ***bbbcaa
// leetcode -> leetcodocteel
// leetcod***e**
// o -> 0
// od -> dod / dod -> 1
// cod -> codoc / docod -> 2
// code -> codedoc / edocode -> 2+1=3
// tcod -> tcodoct / doctcod -> 2+1=3
// tcode -> tcodedoct / edoctcode -> 3+1=4
// etcode = e{tcod}e -> e{tcodoct / doctcod}e -> 3
// etcod -> 1+{tcod} -> 1+3=4
// eetcod -> docteetcod 4 ?/ eetcodoctee 5
// eetcode -> edocteetcode 5 / eetcodedoctee 6 -> e{etcod}e 4 = e{etcodocte}e
// leetcod -> 1+{eetcod} -> 5
// leetcode -> 1+{eetcode} 1+4=5
// aboba
// a -> 0
// ab -> 1
// abo -> min({ab}+1, 1+{bo}) =2
// abob -> min(1+{bob}, {abo} +1)=1
// aboba -> min(0 + {bob}, 1+{abob}, 1+{boba}) = 0
val cache = mutableMapOf<Pair<Int, Int>, Int>()
fun dfs(from: Int, to: Int): Int {
if (from > to || from < 0 || to > s.lastIndex) return -1
if (from == to) return 0
if (from + 1 == to) return if (s[from] == s[to]) 0 else 1
return cache.getOrPut(from to to) {
if (s[from] == s[to]) return@getOrPut dfs(from + 1, to - 1)
val one = dfs(from + 1, to)
val two = dfs(from, to - 1)
when {
one != -1 && two != -1 -> 1 + minOf(one, two)
one != -1 -> 1 + one
two != -1 -> 1 + two
else -> -1
}
}
}
return dfs(0, s.lastIndex)
}
https://t.me/leetcode_daily_unstoppable/188
Let’s add chars one by one. Single char is a palindrome. Two chars are palindrome if they are equal, and not if not: \(insertions_{ab} =\begin{cases}
0, & \text{if a==b}\\
1
\end{cases}\). While adding a new character, we choose the minimum insertions. For example, aboba
:
// aboba
// a -> 0
// ab -> 1
// abo -> min({ab}+1, 1+{bo}) =2
// abob -> min(1+{bob}, {abo} +1)=1
// aboba -> min(0 + {bob}, 1+{abob}, 1+{boba}) = 0
So, the DP equation is the following \(dp_{i,j} = min(0 + dp_{i+1, j-1}, 1 + dp_{i+1, j}, 1 + dp_{i, j-1}\), where DP - is the minimum number of insertions.
Just DFS and cache.
fun profitableSchemes(n: Int, minProfit: Int, group: IntArray, profit: IntArray): Int {
val cache = Array(group.size) { Array(n + 1) { Array(minProfit + 1) { -1 } } }
fun dfs(curr: Int, guys: Int, cashIn: Int): Int {
if (guys < 0) return 0
val cash = minOf(cashIn, minProfit)
if (curr == group.size) return if (cash == minProfit) 1 else 0
with(cache[curr][guys][cash]) { if (this != -1) return@dfs this }
val notTake = dfs(curr + 1, guys, cash)
val take = dfs(curr + 1, guys - group[curr], cash + profit[curr])
val res = (notTake + take) % 1_000_000_007
cache[curr][guys][cash] = res
return res
}
return dfs(0, n, 0)
}
https://t.me/leetcode_daily_unstoppable/187
For every new item, j
we can decide to take it or not take it. Given the inputs of how many guys
we have and how much cash
already earned, the result is always the same: \(count_j = notTake_j(cash, guys) + take_j(cash + profit[j], guys - group[j])\)
Do DFS and cache result in an array.
662. Maximum Width of Binary Tree medium
fun widthOfBinaryTree(root: TreeNode?): Int =
with(ArrayDeque<Pair<Int, TreeNode>>()) {
root?.let { add(0 to it) }
var width = 0
while (isNotEmpty()) {
var first = peek()
var last = last()
width = maxOf(width, last.first - first.first + 1)
repeat(size) {
val (x, node) = poll()
node.left?.let { add(2 * x + 1 to it) }
node.right?.let { add(2 * x + 2 to it) }
}
}
width
}
https://t.me/leetcode_daily_unstoppable/186
For every node, positions of it’s left child is \(2x +1\) and right is \(2x + 2\)
We can do BFS and track node positions.
1372. Longest ZigZag Path in a Binary Tree medium
fun longestZigZag(root: TreeNode?): Int {
var max = 0
fun dfs(n: TreeNode?, len: Int, dir: Int) {
max = maxOf(max, len)
if (n == null) return@dfs
when (dir) {
0 -> {
dfs(n?.left, 0, -1)
dfs(n?.right, 0, 1)
}
1 -> {
dfs(n?.left, len + 1, -1)
dfs(n?.right, 0, 1)
}
-1 -> {
dfs(n?.right, len + 1, 1)
dfs(n?.left, 0, -1)
}
}
}
dfs(root, 0, 0)
return max
}
https://t.me/leetcode_daily_unstoppable/185
Search all the possibilities with DFS
Compute the max
as you go
height
we traverse the full tree1768. Merge Strings Alternately easy
fun mergeAlternately(word1: String, word2: String): String =
(word1.asSequence().zip(word2.asSequence()) { a, b -> "$a$b" } +
word1.drop(word2.length) + word2.drop(word1.length))
.joinToString("")
https://t.me/leetcode_daily_unstoppable/184
Do what is asked. Handle the tail.
zip
operatordrop
1431. Kids With the Greatest Number of Candies easy
fun kidsWithCandies(candies: IntArray, extraCandies: Int): List<Boolean> =
candies.max()?.let { max ->
candies.map { it + extraCandies >= max}
} ?: listOf()
https://t.me/leetcode_daily_unstoppable/183
We can just find the maximum and then try to add extra to every kid and check
Let’s write the code
1639. Number of Ways to Form a Target String Given a Dictionary hard
fun numWays(words: Array<String>, target: String): Int {
val freq = Array(words[0].length) { LongArray(26) }
for (i in 0..words[0].lastIndex)
words.forEach { freq[i][it[i].toInt() - 'a'.toInt()]++ }
val cache = Array(words[0].length) { LongArray(target.length) { -1L } }
val m = 1_000_000_007L
fun dfs(wpos: Int, tpos: Int): Long {
if (tpos == target.length) return 1L
if (wpos == words[0].length) return 0L
if (cache[wpos][tpos] != -1L) return cache[wpos][tpos]
val curr = target[tpos].toInt() - 'a'.toInt()
val currFreq = freq[wpos][curr]
val take = if (currFreq == 0L) 0L else
dfs(wpos + 1, tpos + 1)
val notTake = dfs(wpos + 1, tpos)
val mul = (currFreq * take) % m
val res = (mul + notTake) % m
cache[wpos][tpos] = res
return res
}
return dfs(0, 0).toInt()
}
https://t.me/leetcode_daily_unstoppable/182
Consider an example: bbc aaa ccc, target = ac
. We have 5 ways to form the ac
:
// bbc aaa ccc ac
// a c
// a c
// c a
// a c
// c a
Looking at this, we deduce, that only count of every character at every position matter.
// 0 -> 1b 1a 1c
// 1 -> 1b 1a 1c
// 2 -> 1a 2c
To form ac
we can start from position 0
or from 1
. If we start at 0
, we have one c
at 1 plus two c
at 2. And if we start at 1
we have two c
at 3.
\(DP_{i,j} = Freq * DP_{i + 1, j + 1} + DP_{i + 1, j}\)
freq
array - count of each character at each positionArray
for faster cachelong
to avoid overflow
2218. Maximum Value of K Coins From Piles hard
fun maxValueOfCoins(piles: List<List<Int>>, k: Int): Int {
val cache = Array(piles.size) { mutableListOf<Long>() }
fun dfs(pile: Int, taken: Int): Long {
if (taken >= k || pile >= piles.size) return 0
if (cache[pile].size > taken) return cache[pile][taken]
var max = dfs(pile + 1, taken)
var sum = 0L
for (j in 0..piles[pile].lastIndex) {
val newTaken = taken + j + 1
if (newTaken > k) break
sum += piles[pile][j]
max = maxOf(max, sum + dfs(pile + 1, newTaken))
}
cache[pile].add(max)
return max
}
return dfs(0, 0).toInt()
}
https://t.me/leetcode_daily_unstoppable/181
Given the current pile, we can assume there is an optimal maximum value of the piles to the right of the current for every given number of k
.
We can cache the result by the keys of every pile to taken
516. Longest Palindromic Subsequence medium
fun longestPalindromeSubseq(s: String): Int {
// b + abcaba
// b + ab_ab_
// b + a_cab_
// acbbc + a -> [acbbc]a x[from]==x[to]?1 + p[from+1][to-1]
val p = Array(s.length) { Array(s.length) { 0 } }
for (i in s.lastIndex downTo 0) p[i][i] = 1
for (from in s.lastIndex - 1 downTo 0)
for (to in from + 1..s.lastIndex)
p[from][to] = if (s[from] == s[to]) {
2 + if (to == from + 1) 0 else p[from + 1][to - 1]
} else {
maxOf(p[from][to - 1], p[from + 1][to])
}
return p[0][s.lastIndex]
}
https://t.me/leetcode_daily_unstoppable/180
Simple DFS would not work as it will take \(O(2^n)\) steps.
Consider the sequence: acbbc
and a new element a
. The already existing largest palindrome is cbbc
. When adding a new element, we do not care about what is inside between a..a
, just the largest value of it.
So, there is a DP equation derived from this observation: \(p[i][j] = eq ? 2 + p[i+1][j-1] : max(p[i][j-1], p[i+1][j])\).
For cleaner code:
p[i][i] = 1
0
and lastIndex
from iterationto = from + 1
946. Validate Stack Sequences medium
fun validateStackSequences(pushed: IntArray, popped: IntArray): Boolean =
with(Stack<Int>()) {
var pop = 0
pushed.forEach {
push(it)
while (isNotEmpty() && peek() == popped[pop]) {
pop()
pop++
}
}
isEmpty()
}
https://t.me/leetcode_daily_unstoppable/179
Do simulation using a Stack.
pop
71. Simplify Path medium
fun simplifyPath(path: String): String =
"/" + Stack<String>().apply {
path.split("/").forEach {
when (it) {
".." -> if (isNotEmpty()) pop()
"." -> Unit
"" -> Unit
else -> push(it)
}
}
}.joinToString("/")
https://t.me/leetcode_daily_unstoppable/178
We can simulate what each of the .
and ..
commands do by using a Stack
.
/
2390. Removing Stars From a String medium
fun removeStars(s: String): String = StringBuilder().apply {
s.forEach {
if (it == '*') setLength(length - 1)
else append(it)
}
}.toString()
https://t.me/leetcode_daily_unstoppable/177
Iterate over a string. When *
symbol met, remove last character, otherwise add it.
Stack
, or just StringBuilder
20. Valid Parentheses medium
fun isValid(s: String): Boolean = with(Stack<Char>()) {
val opened = hashSetOf('(', '[', '{')
val match = hashMapOf(')' to '(' , ']' to '[', '}' to '{')
!s.any { c ->
when {
c in opened -> false.also { push(c) }
isEmpty() -> true
else -> pop() != match[c]
}
} && isEmpty()
}
Walk the string and push brackets to the stack. When bracket is closing, pop from it.
1857. Largest Color Value in a Directed Graph hard
fun largestPathValue(colors: String, edges: Array<IntArray>): Int {
if (edges.isEmpty()) return if (colors.isNotEmpty()) 1 else 0
val fromTo = mutableMapOf<Int, MutableList<Int>>()
edges.forEach { (from, to) -> fromTo.getOrPut(from) { mutableListOf() } += to }
val cache = mutableMapOf<Int, IntArray>()
var haveCycle = false
fun dfs(curr: Int, visited: HashSet<Int> = HashSet()): IntArray {
return cache.getOrPut(curr) {
val freq = IntArray(26)
if (visited.add(curr)) {
fromTo.remove(curr)?.forEach {
val childFreq = dfs(it, visited)
for (i in 0..25) freq[i] = maxOf(childFreq[i], freq[i])
}
freq[colors[curr].toInt() - 'a'.toInt()] += 1
} else haveCycle = true
freq
}
}
var max = 0
edges.forEach { (from, to) -> max = maxOf(max, dfs(from).max()!!) }
return if (haveCycle) -1 else max
}
https://t.me/leetcode_daily_unstoppable/175
For each node, there is only one answer of the maximum count of the same color. For each parent, \(c_p = max(freq_{child})+colors[curr]\). We can cache the result and compute it using DFS and selecting maximum count from all the children.
visited
set to detect cycles
133. Clone Graph medium
fun cloneGraph(node: Node?): Node? {
if (node == null) return null
val oldToNew = mutableMapOf<Node, Node>()
fun dfs(n: Node) {
if (oldToNew[n] == null) {
oldToNew[n] = Node(n.`val`)
n.neighbors.forEach {
if (it != null) dfs(it)
}
}
}
fun dfs2(n: Node) {
oldToNew[n]!!.apply {
if (neighbors.isEmpty()) {
n.neighbors.forEach {
if (it != null) {
neighbors.add(oldToNew[it])
dfs2(it)
}
}
}
}
}
dfs(node)
dfs2(node)
return oldToNew[node]
}
https://t.me/leetcode_daily_unstoppable/174
We can map every old
node to its new
node. Then one DFS for the creation, another for the linking.
visited
set by checking if a new node already has filled its neighbors.
1020. Number of Enclaves medium
fun numEnclaves(grid: Array<IntArray>): Int {
val visited = HashSet<Pair<Int, Int>>()
fun dfs(x: Int, y: Int): Int {
return if (x < 0 || y < 0 || x > grid[0].lastIndex || y > grid.lastIndex) 0
else if (grid[y][x] == 1 && visited.add(x to y))
1 + dfs(x - 1, y) + dfs(x + 1, y) + dfs(x, y - 1) + dfs(x, y + 1)
else 0
}
for (y in 0..grid.lastIndex) {
dfs(0, y)
dfs(grid[0].lastIndex, y)
}
for (x in 0..grid[0].lastIndex) {
dfs(x, 0)
dfs(x, grid.lastIndex)
}
var count = 0
for (y in 0..grid.lastIndex)
for(x in 0..grid[0].lastIndex)
count += dfs(x, y)
return count
}
https://t.me/leetcode_daily_unstoppable/173
Walk count all the 1
cells using DFS and a visited set.
We can use visited
set, or modify the grid or use Union-Find.
To exclude the borders, we can visit them first with DFS.
1254. Number of Closed Islands medium
fun closedIsland(grid: Array<IntArray>): Int {
val visited = HashSet<Pair<Int, Int>>()
val seen = HashSet<Pair<Int, Int>>()
fun dfs(x: Int, y: Int): Boolean {
seen.add(x to y)
if (x >= 0 && y >= 0 && x < grid[0].size && y < grid.size
&& grid[y][x] == 0 && visited.add(x to y)) {
var isBorder = x == 0 || y == 0 || x == grid[0].lastIndex || y == grid.lastIndex
isBorder = dfs(x - 1, y) || isBorder
isBorder = dfs(x, y - 1) || isBorder
isBorder = dfs(x + 1, y) || isBorder
isBorder = dfs(x, y + 1) || isBorder
return isBorder
}
return false
}
var count = 0
for (y in 0..grid.lastIndex)
for (x in 0..grid[0].lastIndex)
if (grid[y][x] == 0 && seen.add(x to y) && !dfs(x, y)) count++
return count
}
https://t.me/leetcode_daily_unstoppable/172
Use hint #1, if we don’t count islands on the borders, we get the result. Now, just count all connected 0
cells that didn’t connect to the borders. We can use DFS or Union-Find.
DFS will solve the problem.
2439. Minimize Maximum of Array medium
fun minimizeArrayValue(nums: IntArray): Int {
// 5 4 3 2 1 -> 5
// 1 2 3 4 5 -> 3
// 1 2 3 6 3
// 1 2 6 3 3
// 1 5 3 3 3
// 3 3 3 3 3
fun canArrangeTo(x: Long): Boolean {
var diff = 0L
for (i in nums.lastIndex downTo 0)
diff = maxOf(0L, nums[i].toLong() - x + diff)
return diff == 0L
}
var lo = 0
var hi = Int.MAX_VALUE
var min = Int.MAX_VALUE
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (canArrangeTo(mid.toLong())) {
min = minOf(min, mid)
hi = mid - 1
} else lo = mid + 1
}
return min
}
https://t.me/leetcode_daily_unstoppable/171
Observing the pattern, we can see, that any number from the end
can be passed to the start
of the array. One idea is to use two pointers, one pointing to the biggest
value, another to the smallest
. Given that biggest and smallest values changes, it will take \(O(nlog_2(n))\) time to maintain such sorted structure.
Another idea, is that for any given maximum value
we can walk an array from the end to the start and change values to be no bigger than it. This operation takes \(O(n)\) time, and with the growth of the maximum value
also increases a possibility to comply for all the elements. So, we can binary search in that space.
lo == hi
lo
and hi
min = minOf(min, mid)
mid + 1
and mid - 1
2405. Optimal Partition of String medium
var mask = 0
fun partitionString(s: String): Int = 1 + s.count {
val bit = 1 shl (it.toInt() - 'a'.toInt())
(mask and bit != 0).also {
if (it) mask = 0
mask = mask or bit
}
}
https://t.me/leetcode_daily_unstoppable/170
Expand all the intervals until they met a duplicate character. This will be the optimal solution, as the minimum of the intervals correlates with the maximum of each interval length.
hashset
, [26]
array or simple 32-bit
mask to store visited flags for character
881. Boats to Save People medium
fun numRescueBoats(people: IntArray, limit: Int): Int {
people.sort()
var count = 0
var lo = 0
var hi = people.lastIndex
while (lo <= hi) {
if (lo < hi && people[hi] + people[lo] <= limit) lo++
hi--
count++
}
return count
}
https://t.me/leetcode_daily_unstoppable/169
The optimal strategy comes from an intuition: for each people[hi]
of a maximum weight, we can or can not add the one man people[lo]
of a minimum weight.
Sort an array and move two pointers lo
and hi
.
lo == hi
2300. Successful Pairs of Spells and Potions medium
fun successfulPairs(spells: IntArray, potions: IntArray, success: Long): IntArray {
potions.sort()
return IntArray(spells.size) { ind ->
var lo = 0
var hi = potions.lastIndex
var minInd = potions.size
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (potions[mid].toLong() * spells[ind].toLong() >= success) {
minInd = minOf(minInd, mid)
hi = mid - 1
} else lo = mid + 1
}
potions.size - minInd
}
}
https://t.me/leetcode_daily_unstoppable/168
If we sort potions
, we can find the lowest possible value of spell[i]*potion[i]
that is >= success
. All other values are bigger by the math multiplication property.
potions
lowest
indexlong
to solve the integer overflow
lo
and hi
lo == hi
minInd
lo
and the hi
mid
to not overflow
704. Binary Search easy
fun search(nums: IntArray, target: Int): Int {
var lo = 0
var hi = nums.lastIndex
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (nums[mid] == target) return mid
if (nums[mid] < target) lo = mid + 1
else hi = mid - 1
}
return -1
}
https://t.me/leetcode_daily_unstoppable/167
Just write binary search.
For more robust code:
lo..hi
lo == hi
== target
mid
without the integer overflowmid +
or mid - 1
target
1444. Number of Ways of Cutting a Pizza hard
data class Key(val x: Int, val y: Int, val c: Int)
fun ways(pizza: Array<String>, k: Int): Int {
val havePizza = Array(pizza.size) { Array<Int>(pizza[0].length) { 0 } }
val lastX = pizza[0].lastIndex
val lastY = pizza.lastIndex
for (y in lastY downTo 0) {
var sumX = 0
for (x in lastX downTo 0) {
sumX += if (pizza[y][x] == 'A') 1 else 0
havePizza[y][x] = sumX + (if (y == lastY) 0 else havePizza[y + 1][x])
}
}
val cache = mutableMapOf<Key, Int>()
fun dfs(x: Int, y: Int, c: Int): Int {
return cache.getOrPut(Key(x, y, c)) {
if (c == 0) return@getOrPut if (havePizza[y][x] > 0) 1 else 0
var res = 0
for (xx in x + 1..lastX) if (havePizza[y][x] > havePizza[y][xx])
res = (res + dfs(xx, y, c - 1)) % 1_000_000_007
for (yy in y + 1..lastY) if (havePizza[y][x] > havePizza[yy][x])
res = (res + dfs(x, yy, c - 1)) % 1_000_000_007
return@getOrPut res
}
}
return dfs(0, 0, k - 1)
}
https://t.me/leetcode_daily_unstoppable/165
The tricky problem is to how to program a number of cuts.
We can do the horizontal and vertical cuts decreasing available number k
and tracking if we have any apples before
and any apples after
the cut. To track this, we can precompute a prefix sum
of the apples, by each top-left
corner to the end of the pizza. The stopping condition of the DFS is if we used all available cuts.
sumX
, then you move by column and reuse the result of the previous row.x,y
in the arguments and from the other side of the cut xx,y
or x, yy
.
87. Scramble String hard
data class Key(val afrom: Int, val ato: Int, val bfrom: Int, val bto: Int)
fun isScramble(a: String, b: String): Boolean {
val dp = HashMap<Key, Boolean>()
fun dfs(key: Key): Boolean {
return dp.getOrPut(key) {
val (afrom, ato, bfrom, bto) = key
val alength = ato - afrom
val blength = bto - bfrom
if (alength != blength) return@getOrPut false
var same = true
for (i in 0..alength)
if (a[afrom + i] != b[bfrom + i]) same = false
if (same) return@getOrPut true
for (i in afrom..ato - 1) {
if (dfs(Key(afrom, i, bfrom, bfrom + (i - afrom)))
&& dfs(Key(i + 1, ato, bfrom + (i - afrom) + 1, bto))) return@getOrPut true
if (dfs(Key(afrom, i, bto - (i - afrom), bto))
&& dfs(Key(i + 1, ato, bfrom, bto - (i - afrom) - 1))) return@getOrPut true
}
return@getOrPut false
}
}
return dfs(Key(0, a.lastIndex, 0, b.lastIndex))
}
https://t.me/leetcode_daily_unstoppable/164
This is not a permutation’s problem, as there are examples when we can’t scramble two strings consisting of the same characters. We can simulate the process and search the result using DFS.
A simple approach is to concatenate strings, but in Kotlin it gives TLE, so we need bottom up approach, or just operate with indices.
@getOrPut
when exiting lambda
fun maxSatisfaction(satisfaction: IntArray): Int {
satisfaction.sort()
var max = 0
var curr = 0
var diff = 0
for (i in satisfaction.lastIndex downTo 0) {
diff += satisfaction[i]
curr += diff
max = maxOf(max, curr)
}
return max
}
https://t.me/leetcode_daily_unstoppable/163
Looking at the problem data examples, we intuitively deduce that the larger the number, the further it goes. We need to sort the array. With the negative numbers, we must compare all the results, excluding array prefixes.
The naive \(O(n^2)\) solution will work. However, there is an optimal one if we simply go from the end.
983. Minimum Cost For Tickets medium
fun mincostTickets(days: IntArray, costs: IntArray): Int {
val cache = IntArray(days.size) { -1 }
fun dfs(day: Int): Int {
if (day >= days.size) return 0
if (cache[day] != -1) return cache[day]
var next = day
while (next < days.size && days[next] - days[day] < 1) next++
val costOne = costs[0] + dfs(next)
while (next < days.size && days[next] - days[day] < 7) next++
val costSeven = costs[1] + dfs(next)
while (next < days.size && days[next] - days[day] < 30) next++
val costThirty = costs[2] + dfs(next)
return minOf(costOne, costSeven, costThirty).also { cache[day] = it}
}
return dfs(0)
}
https://t.me/leetcode_daily_unstoppable/162
For each day we can choose between tickets. Explore all of them and then choose minimum of the cost.
Let’s write DFS with memoization algorithm as it is simple to understand.
64. Minimum Path Sum medium
fun minPathSum(grid: Array<IntArray>): Int {
val cache = mutableMapOf<Pair<Int, Int>, Int>()
fun dfs(xy: Pair<Int, Int>): Int {
return cache.getOrPut(xy) {
val (x, y) = xy
val curr = grid[y][x]
if (x == grid[0].lastIndex && y == grid.lastIndex) curr else
minOf(
if (x < grid[0].lastIndex) curr + dfs((x + 1) to y)
else Int.MAX_VALUE,
if (y < grid.lastIndex) curr + dfs(x to (y + 1))
else Int.MAX_VALUE
)
}
}
return dfs(0 to 0)
}
https://t.me/leetcode_daily_unstoppable/161
On each cell of the grid, there is only one minimum path sum. So, we can memorize it. Or we can use a bottom up DP approach.
Use DFS + memo, careful with the ending condition.
2360. Longest Cycle in a Graph hard
fun longestCycle(edges: IntArray): Int {
var maxLen = -1
fun checkCycle(node: Int) {
var x = node
var len = 0
do {
if (x != edges[x]) len++
x = edges[x]
} while (x != node)
if (len > maxLen) maxLen = len
}
val visited = HashSet<Int>()
fun dfs(curr: Int, currPath: HashSet<Int>) {
val isCurrentLoop = !currPath.add(curr)
if (curr != -1 && !isCurrentLoop && visited.add(curr)) {
dfs(edges[curr], currPath)
} else if (curr != -1 && isCurrentLoop) checkCycle(curr)
}
for (i in 0..edges.lastIndex) dfs(i, HashSet<Int>())
return maxLen
}
https://t.me/leetcode_daily_unstoppable/160
We can walk all paths once and track the cycles with the DFS.
checkCycle
corner cases.
2316. Count Unreachable Pairs of Nodes in an Undirected Graph medium
fun countPairs(n: Int, edges: Array<IntArray>): Long {
val uf = IntArray(n) { it }
val sz = LongArray(n) { 1L }
fun root(x: Int): Int {
var n = x
while (uf[n] != n) n = uf[n]
uf[x] = n
return n
}
fun union(a: Int, b: Int) {
val rootA = root(a)
val rootB = root(b)
if (rootA != rootB) {
uf[rootB] = rootA
sz[rootA] += sz[rootB]
sz[rootB] = 0L
}
}
edges.forEach { (from, to) -> union(from, to) }
// 1 2 4 = 1*2 + 1*4 + 2*4 = 1*2 + (1+2)*4
var sum = 0L
var count = 0L
sz.forEach { // 2 2 4 = 2*2 + 2*4 + 2*4 = 2*2 + (2+2)*4
count += sum * it
sum += it
}
return count
}
https://t.me/leetcode_daily_unstoppable/159
To find connected components sizes, we can use Union-Find.
To count how many pairs, we need to derive the formula, observing the pattern. Assume we have groups sizes 3, 4, 5
, the number of pairs is the number of pairs between 3,4
+ the number of pairs between 4,5
+ between 3,5
. Or, \(count(a,b,c) = count(a,b) + count(b,c) + count(a,c)\) where \(count(a,b) = a*b\). So, \(count_{abc} = ab + bc + ac = ab + (a + b)c = count_{ab} + (a+b)c\), or \(count_i = count_{i-1} + x_i*\sum_{j=0}^{i}x\)
root
time complexity
1466. Reorder Routes to Make All Paths Lead to the City Zero medium
fun minReorder(n: Int, connections: Array<IntArray>): Int {
val edges = mutableMapOf<Int, MutableList<Int>>()
connections.forEach { (from, to) ->
edges.getOrPut(from, { mutableListOf() }) += to
edges.getOrPut(to, { mutableListOf() }) += -from
}
val visited = HashSet<Int>()
var count = 0
with(ArrayDeque<Int>().apply { add(0) }) {
fun addNext(x: Int) {
if (visited.add(Math.abs(x))) {
add(Math.abs(x))
if (x > 0) count++
}
}
while (isNotEmpty()) {
repeat(size) {
val from = poll()
edges[from]?.forEach { addNext(it) }
edges[-from]?.forEach { addNext(it) }
}
}
}
return count
}
https://t.me/leetcode_daily_unstoppable/158
If our roads are undirected, the problem is simple: traverse with BFS from 0
and count how many roads are in the opposite direction.
We can use data structure or just use sign to encode the direction.
1319. Number of Operations to Make Network Connected medium
fun makeConnected(n: Int, connections: Array<IntArray>): Int {
var extraCables = 0
var groupsCount = n
val uf = IntArray(n) { it }
fun findRoot(x: Int): Int {
var n = x
while (uf[n] != n) n = uf[n]
uf[x] = n
return n
}
fun connect(a: Int, b: Int) {
val rootA = findRoot(a)
val rootB = findRoot(b)
if (rootA == rootB) {
extraCables++
return
}
uf[rootB] = rootA
groupsCount--
}
connections.forEach { (from, to) -> connect(from, to) }
return if (extraCables < groupsCount - 1) -1 else groupsCount - 1
}
https://t.me/leetcode_daily_unstoppable/157
The number of cables we need is the number of disconnected groups of connected computers. Cables can be taken from the computers that have extra connections. We can do this using BFS/DFS and tracking visited set, counting extra cables if already visited node is in connection. Another solution is to use Union-Find for the same purpose.
findRoot
use path compression: uf[x] = n
2492. Minimum Score of a Path Between Two Cities medium
fun minScore(n: Int, roads: Array<IntArray>): Int {
val uf = Array(n + 1) { it }
val minDist = Array(n + 1) { Int.MAX_VALUE }
fun findRoot(x: Int): Int {
var n = x
while (uf[n] != n) n = uf[n]
uf[x] = n
return n
}
fun union(a: Int, b: Int, dist: Int) {
val rootA = findRoot(a)
val rootB = findRoot(b)
uf[rootB] = rootA
minDist[rootA] = minOf(minDist[rootA], minDist[rootB], dist)
}
roads.forEach { (from, to, dist) -> union(from, to, dist) }
return minDist[findRoot(1)]
}
https://t.me/leetcode_daily_unstoppable/156
Observing the problem definition, we don’t care about the path, but only about the minimum distance in a connected subset containing 1
and n
. This can be solved by simple BFS, which takes \(O(V+E)\) time and space. But ideal data structure for this problem is Union-Find.
find
operation of Union-Find is difficult. https://algs4.cs.princeton.edu/15uf/Connect all roads and update minimums in the Union-Find data structure. Use simple arrays for both connections and minimums.
2348. Number of Zero-Filled Subarrays medium
fun zeroFilledSubarray(nums: IntArray): Long {
var currCount = 0L
var sum = 0L
nums.forEach {
if (it == 0) currCount++ else currCount = 0L
sum += currCount
}
return sum
}
https://t.me/leetcode_daily_unstoppable/155
Consider the following sequence: 0
, 00
, 000
. Each time we are adding another element to the end of the previous. For 0
count of subarrays \(c_1 = 1\), for 00
it is \(c_2 = c_1 + z_2\), where \(z_2\) is a number of zeros. So, the math equation is \(c_i = c_{i-1} + z_i\), or \(c_n = \sum_{i=0}^{n}z_i\)
We can count subarray sums, then add them to the result, or we can just skip directly to adding to the result.
fun canPlaceFlowers(flowerbed: IntArray, n: Int): Boolean {
var count = 0
if (flowerbed.size == 1 && flowerbed[0] == 0) count++
if (flowerbed.size >= 2 && flowerbed[0] == 0 && flowerbed[1] == 0) {
flowerbed[0] = 1
count++
}
for (i in 1..flowerbed.lastIndex - 1) {
if (flowerbed[i] == 0 && flowerbed[i - 1] == 0 && flowerbed[i + 1] == 0) {
flowerbed[i] = 1
count++
}
}
if (flowerbed.size >= 2 && flowerbed[flowerbed.lastIndex] == 0 && flowerbed[flowerbed.lastIndex - 1] == 0) count++
return count >= n
}
https://t.me/leetcode_daily_unstoppable/154
We can plant flowers greedily in every vacant place. This will be the maximum result because if we skip one item, the result is the same for even number of places or worse for odd.
211. Design Add and Search Words Data Structure medium
class Trie {
val next = Array<Trie?>(26) { null }
fun Char.ind() = toInt() - 'a'.toInt()
operator fun get(c: Char): Trie? = next[c.ind()]
operator fun set(c: Char, t: Trie) { next[c.ind()] = t }
var isWord = false
}
class WordDictionary(val root: Trie = Trie()) {
fun addWord(word: String) {
var t = root
word.forEach { t = t[it] ?: Trie().apply { t[it] = this } }
t.isWord = true
}
fun search(word: String): Boolean = with(ArrayDeque<Trie>().apply { add(root) }) {
!word.any { c ->
repeat(size) {
val t = poll()
if (c == '.') ('a'..'z').forEach { t[it]?.let { add(it) } }
else t[c]?.let { add(it) }
}
isEmpty()
} && any { it.isWord }
}
}
https://t.me/leetcode_daily_unstoppable/153
We are already familiar with a Trie
data structure, however there is a wildcard
feature added. We have two options: add wildcard for every character in addWord
method in \(O(w26^w)\) time and then search in \(O(w)\) time, or just add a word to Trie
in \(O(w)\) time and then search in \(O(w26^d)\) time, where \(d\) - is a wildcards count. In the description, there are at most 3
dots, so we choose the second option.
Let’s try to write it in a Kotlin way, using as little words as possible.
1472. Design Browser History medium
class BrowserHistory(homepage: String) {
val list = mutableListOf(homepage)
var curr = 0
var last = 0
fun visit(url: String) {
curr++
if (curr == list.size) {
list.add(url)
} else {
list[curr] = url
}
last = curr
}
fun back(steps: Int): String {
curr = (curr - steps).coerceIn(0, last)
return list[curr]
}
fun forward(steps: Int): String {
curr = (curr + steps).coerceIn(0, last)
return list[curr]
}
}
https://t.me/leetcode_daily_unstoppable/152
Simple solution with array list will work, just not very optimal for the memory.
Just implement it.
208. Implement Trie (Prefix Tree) medium
class Trie() {
val root = Array<Trie?>(26) { null }
fun Char.ind() = toInt() - 'a'.toInt()
operator fun get(c: Char): Trie? = root[c.ind()]
operator fun set(c: Char, v: Trie) { root[c.ind()] = v }
var isWord = false
fun insert(word: String) {
var t = this
word.forEach { t = t[it] ?: Trie().apply { t[it] = this} }
t.isWord = true
}
fun String.search(): Trie? {
var t = this@Trie
forEach { t = t[it] ?: return@search null }
return t
}
fun search(word: String) = word.search()?.isWord ?: false
fun startsWith(prefix: String) = prefix.search() != null
}
https://t.me/leetcode_daily_unstoppable/151
Trie is a common known data structure and all must know how to implement it.
Let’s try to write it Kotlin-way
106. Construct Binary Tree from Inorder and Postorder Traversal medium
fun buildTree(inorder: IntArray, postorder: IntArray): TreeNode? {
val inToInd = inorder.asSequence().mapIndexed { i, v -> v to i }.toMap()
var postTo = postorder.lastIndex
fun build(inFrom: Int, inTo: Int): TreeNode? {
if (inFrom > inTo || postTo < 0) return null
return TreeNode(postorder[postTo]).apply {
val inInd = inToInd[postorder[postTo]]!!
postTo--
right = build(inInd + 1, inTo)
left = build(inFrom, inInd - 1)
}
}
return build(0, inorder.lastIndex)
}
https://t.me/leetcode_daily_unstoppable/150
Postorder traversal gives us the root of every current subtree. Next, we need to find this value in inorder traversal: from the left of it will be the left subtree, from the right - right.
postTo
variable as we go in the reverse-postorder: from the right to the left.958. Check Completeness of a Binary Tree medium
data class R(val min: Int, val max: Int, val complete: Boolean)
fun isCompleteTree(root: TreeNode?): Boolean {
fun dfs(n: TreeNode): R {
with(n) {
if (left == null && right != null) return R(0, 0, false)
if (left == null && right == null) return R(0, 0, true)
val (leftMin, leftMax, leftComplete) = dfs(left)
if (!leftComplete) return R(0, 0, false)
if (right == null) return R(0, leftMax + 1, leftMin == leftMax && leftMin == 0)
val (rightMin, rightMax, rightComplete) = dfs(right)
if (!rightComplete) return R(0, 0, false)
val isComplete = leftMin == rightMin && rightMin == rightMax
|| leftMin == leftMax && leftMin == rightMin + 1
return R(1 + minOf(leftMin, rightMin), 1 + maxOf(leftMax, rightMax), isComplete)
}
}
return root == null || dfs(root).complete
}
https://t.me/leetcode_daily_unstoppable/149
For each node, we can compute it’s left and right child min
and max
depth, then compare them.
Right depth must not be larger than left. There are no corner cases, just be careful.
129. Sum Root to Leaf Numbers medium
fun sumNumbers(root: TreeNode?): Int = if (root == null) 0 else {
var sum = 0
fun dfs(n: TreeNode, soFar: Int) {
with(n) {
val x = soFar * 10 + `val`
if (left == null && right == null) sum += x
if (left != null) dfs(left, x)
if (right != null) dfs(right, x)
}
}
dfs(root, 0)
sum
}
https://t.me/leetcode_daily_unstoppable/148
Just make DFS and add to the sum if the node is a leaf.
The most trivial way is to keep sum
variable outside the dfs function.
101. Symmetric Tree easy
data class H(val x: Int?)
fun isSymmetric(root: TreeNode?): Boolean {
with(ArrayDeque<TreeNode>().apply { root?.let { add(it) } }) {
while (isNotEmpty()) {
val stack = Stack<H>()
val sz = size
repeat(sz) {
if (sz == 1 && peek().left?.`val` != peek().right?.`val`) return false
with(poll()) {
if (sz == 1 || it < sz / 2) {
stack.push(H(left?.`val`))
stack.push(H(right?.`val`))
} else {
if (stack.isEmpty() || stack.pop().x != left?.`val`) return false
if (stack.isEmpty() || stack.pop().x != right?.`val`) return false
}
left?.let { add(it)}
right?.let { add(it)}
}
}
}
}
return true
}
fun isSymmetric2(root: TreeNode?): Boolean {
fun isSymmetric(leftRoot: TreeNode?, rightRoot: TreeNode?): Boolean {
return leftRoot == null && rightRoot == null
|| leftRoot != null && rightRoot != null
&& leftRoot.`val` == rightRoot.`val`
&& isSymmetric(leftRoot.left, rightRoot.right)
&& isSymmetric(leftRoot.right, rightRoot.left)
}
return isSymmetric(root?.left, root?.right)
}
https://t.me/leetcode_daily_unstoppable/147
Recursive solution based on idea that we must compare left.left
with right.right
and left.right
with right.left
.
Iterative solution is just BFS and Stack.
Recursive: just write helper function.
Iterative: save also null
’s to solve corner cases.
fun mergeKLists(lists: Array<ListNode?>): ListNode? {
val root = ListNode(0)
var curr: ListNode = root
val pq = PriorityQueue<ListNode>(compareBy( { it.`val` }))
lists.forEach { if (it != null) pq.add(it) }
while (pq.isNotEmpty()) {
val next = pq.poll()
curr.next = next
next.next?.let { pq.add(it) }
curr = next
}
return root.next
}
fun mergeKLists2(lists: Array<ListNode?>): ListNode? {
fun merge(oneNode: ListNode?, twoNode: ListNode?): ListNode? {
val root = ListNode(0)
var curr: ListNode = root
var one = oneNode
var two = twoNode
while (one != null && two != null) {
if (one.`val` <= two.`val`) {
curr.next = one
one = one.next
} else {
curr.next = two
two = two.next
}
curr = curr.next!!
}
if (one != null) curr.next = one
else if (two != null) curr.next = two
return root.next
}
return lists.fold(null as ListNode?) { r, t -> merge(r, t) }
}
https://t.me/leetcode_daily_unstoppable/146
On each step, we need to choose a minimum from k
variables. The best way to do this is to use PriorityQeueu
Another solution is to just iteratively merge the result
to the next list from the array.
PriorityQueue
solution:PriorityQueue
: \(O(nlog(k))\)PriorityQueue
: \(O(k)\)109. Convert Sorted List to Binary Search Tree medium
fun sortedListToBST(head: ListNode?): TreeNode? {
if (head == null) return null
if (head.next == null) return TreeNode(head.`val`)
var one = head
var twoPrev = head
var two = head
while (one != null && one.next != null) {
one = one.next?.next
twoPrev = two
two = two?.next
}
twoPrev!!.next = null
return TreeNode(two!!.`val`).apply {
left = sortedListToBST(head)
right = sortedListToBST(two!!.next)
}
}
https://t.me/leetcode_daily_unstoppable/145
One way is to convert linked list to array, then just build a binary search tree using divide and conquer technique. This will take \(O(nlog_2(n))\) additional memory, and \(O(n)\) time. We can skip using the array and just compute the middle of the linked list each time.
Compute the middle of the linked list.
fast.next != null
instead of fast != null
)
382. Linked List Random Node medium
class Solution(val head: ListNode?) {
val rnd = Random(0)
var curr = head
fun getRandom(): Int {
val ind = rnd.nextInt(6)
var peek: ListNode? = null
repeat(6) {
curr = curr?.next
if (curr == null) curr = head
if (it == ind) peek = curr
}
return peek!!.`val`
}
}
https://t.me/leetcode_daily_unstoppable/144
Naive solution is trivial. For more interesting solution, you need to look at what others did on leetcode, read an article https://en.wikipedia.org/wiki/Reservoir_sampling and try to understand why it works.
My intuition was: if we need a probability 1/n
, where n
- is a total number of elements, then what if we split all the input into buckets of size k
, then choose from every bucket with probability 1/k
. It seems to work, but only for sizes starting from number 6
for the given input.
We just need to be sure, that number of getRandom
calls are equal to number of buckets n/k
.
Write the naive solution, then go to Wikipedia, and hope you will not get this in the interview.
142. Linked List Cycle II medium
fun detectCycle(head: ListNode?): ListNode? {
var one = head
var two = head
do {
one = one?.next
two = two?.next?.next
} while (two != null && one != two)
if (two == null) return null
one = head
while (one != two) {
one = one?.next
two = two?.next
}
return one
}
https://t.me/leetcode_daily_unstoppable/143
There is a known algorithm to detect a cycle in a linked list. Move slow
pointer one node at a time, and move fast
pointer two nodes at a time. Eventually, if they meet, there is a cycle.
To know the connection point of the cycle, you can also use two pointers: one from where pointers were met, another from the start, and move both of them one node at a time until they meet.
How to derive this yourself?
875. Koko Eating Bananas medium
fun minEatingSpeed(piles: IntArray, h: Int): Int {
fun canEatAll(speed: Long): Boolean {
var time = 0L
piles.forEach {
time += (it.toLong() / speed) + if ((it.toLong() % speed) == 0L) 0L else 1L
}
return time <= h
}
var lo = 1L
var hi = piles.asSequence().map { it.toLong() }.sum()!!
var minSpeed = hi
while (lo <= hi) {
val speed = lo + (hi - lo) / 2
if (canEatAll(speed)) {
minSpeed = minOf(minSpeed, speed)
hi = speed - 1
} else {
lo = speed + 1
}
}
return minSpeed.toInt()
}
https://t.me/leetcode_daily_unstoppable/142
Given the speed
we can count how many hours
take Coco to eat all the bananas. With growth of speed
hours
growth too, so we can binary search in that space.
For more robust binary search:
lo == hi
mid + 1
, mid - 1
m
- is hours
range2187. Minimum Time to Complete Trips medium
fun minimumTime(time: IntArray, totalTrips: Int): Long {
fun tripCount(timeGiven: Long): Long {
var count = 0L
for (t in time) count += timeGiven / t.toLong()
return count
}
var lo = 0L
var hi = time.asSequence().map { it.toLong() * totalTrips }.max()!!
var minTime = hi
while (lo <= hi) {
val timeGiven = lo + (hi - lo) / 2
val trips = tripCount(timeGiven)
if (trips >= totalTrips) {
minTime = minOf(minTime, timeGiven)
hi = timeGiven - 1
} else {
lo = timeGiven + 1
}
}
return minTime
}
https://t.me/leetcode_daily_unstoppable/140
Naive approach is just to simulate the time
running, but given the problem range it is not possible.
However, observing the time
simulation results, we can notice, that by each given time
there is a certain number of trips
. And number of trips
growths continuously with the growth of the time
. This is a perfect condition to do a binary search in a space of the given time
.
With given time
we can calculate number of trips in a \(O(n)\) complexity.
Do a binary search. For the hi
value, we can peak a \(10^7\) or just compute all the time it takes for every bus to trip.
For a more robust binary search:
lo
and hi
lo == hi
mid + 1
, mid - 1
1539. Kth Missing Positive Number easy
fun findKthPositive(arr: IntArray, k: Int): Int {
// 1 2 3 4 5 6 7 8 9 10 11
// * 2 3 4 * * 7 * * * 11
// ^ ^
// 1 2 3 4 5
// 2 3 4 7 11
// 1
// 1
// 1
// 3
// 6
//
// ^ 7 + (5-3) = 9
// arr[m] + (k-diff)
//
// 1 2
// 7 8 k=1
// 6
// 6
var lo = 0
var hi = arr.lastIndex
var res = -1
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
val diff = arr[mid] - mid - 1
if (diff < k) {
res = arr[mid] + (k - diff)
lo = mid + 1
} else {
hi = mid - 1
}
}
return if (res == -1) k else res
}
https://t.me/leetcode_daily_unstoppable/139
Let’s observe an example:
// 1 2 3 4 5 6 7 8 9 10 11
// * 2 3 4 * * 7 * * * 11
For each number at its position, there are two conditions:
num - pos == 0
num - pos == diff > 0
We can observe the pattern and derive the formula for it:
// 1 2 3 4 5
// 2 3 4 7 11
// 1
// 1
// 1
// 3
// 6
//
// ^ 7 + (5-3) = 9
// arr[m] + (k-diff)
One corner case is if the missing numbers are at the beginning of the array:
// 1 2
// 7 8 k=1
// 6
// 6
Then the answer is just a k
.
For more robust binary search code:
lo
and hi
(don’t make of by 1 error)lo == hi
(don’t miss one item arrays)mid + 1
or mid - 1
(don’t fall into an infinity loop)true
(don’t compute it after the search to avoid mistakes)
1345. Jump Game IV hard
fun minJumps(arr: IntArray): Int {
val numToPos = mutableMapOf<Int, MutableList<Int>>()
arr.forEachIndexed { i, n -> numToPos.getOrPut(n, { mutableListOf() }).add(i) }
with(ArrayDeque<Int>().apply { add(0) }) {
var jumps = 0
val visited = HashSet<Int>()
while(isNotEmpty()) {
repeat(size) {
val curr = poll()
if (curr == arr.lastIndex) return jumps
numToPos.remove(arr[curr])?.forEach { if (visited.add(it)) add(it) }
if (curr > 0 && visited.add(curr - 1)) add(curr - 1)
if (curr < arr.lastIndex && visited.add(curr + 1)) add(curr + 1)
}
jumps++
}
}
return 0
}
https://t.me/leetcode_daily_unstoppable/138
Dynamic programming approach wouldn’t work here, as we can tell from position i
is it optimal before visiting both left and right subarrays.
Another way to find the shortest path is to just do Breath-First-Search.
This problem gives TLE until we do one trick:
2444. Count Subarrays With Fixed Bounds hard
fun countSubarrays(nums: IntArray, minK: Int, maxK: Int): Long {
val range = minK..maxK
var i = 0
var sum = 0L
if (minK == maxK) {
var count = 0
for (i in 0..nums.lastIndex) {
if (nums[i] == minK) count++
else count = 0
if (count > 0) sum += count
}
return sum
}
while (i < nums.size) {
val curr = nums[i]
if (curr in range) {
val minInds = TreeSet<Int>()
val maxInds = TreeSet<Int>()
var end = i
while (end < nums.size && nums[end] in range) {
if (nums[end] == minK) minInds.add(end)
else if (nums[end] == maxK) maxInds.add(end)
end++
}
if (minInds.size > 0 && maxInds.size > 0) {
var prevInd = i - 1
while (minInds.isNotEmpty() && maxInds.isNotEmpty()) {
val minInd = minInds.pollFirst()!!
val maxInd = maxInds.pollFirst()!!
val from = minOf(minInd, maxInd)
val to = maxOf(minInd, maxInd)
val remainLenAfter = (end - 1 - to).toLong()
val remainLenBefore = (from - (prevInd + 1)).toLong()
sum += 1L + remainLenAfter + remainLenBefore + remainLenAfter * remainLenBefore
prevInd = from
if (to == maxInd) maxInds.add(to)
else if (to == minInd) minInds.add(to)
}
}
if (i == end) end++
i = end
} else i++
}
return sum
}
and more clever solution:
fun countSubarrays(nums: IntArray, minK: Int, maxK: Int): Long {
var sum = 0L
if (minK == maxK) {
var count = 0
for (i in 0..nums.lastIndex) {
if (nums[i] == minK) count++
else count = 0
if (count > 0) sum += count
}
return sum
}
val range = minK..maxK
// 0 1 2 3 4 5 6 7 8 91011
// 3 7 2 2 2 2 2 1 2 3 2 1
// b
// *...*
// *...*
var border = -1
var posMin = -1
var posMax = -1
for (i in 0..nums.lastIndex) {
when (nums[i]) {
!in range -> border = i
minK -> posMin = i
maxK -> posMax = i
}
if (posMin > border && posMax > border)
sum += minOf(posMin, posMax) - border
}
return sum
}
https://t.me/leetcode_daily_unstoppable/137
First thought is that we can observe only subarrays, where all the elements are in a range min..max
. Next, there are two possible scenarios:
minK==maxK
, our problem is a trivial count of the combinations, \(0 + 1 + .. + (n-1) + n = n*(n+1)/2\)minK != maxK
, we need to take every minK|maxK
pair, and count how many items are in range before
them and how many after
. Then, as we observe the pattern of combinations:
// 0 1 2 3 4 5 6 min=1, max=3
// ------------------
// 1 2 3 2 1 2 3
// 1 2 3 *** 0..2 remainLenAfter = 6 - 2 = 4
// 1 2 3 2
// 1 2 3 2 1
// 1 2 3 2 1 2
// 1 2 3 2 1 2 3
// 3 2 1 *** 2..4 remainLenAfter = 6 - 4 = 2
// 3 2 1 2
// 3 2 1 2 3
// 2 3 2 1 remainLenBefore = 2 - (0 + 1) = 1, sum += 1 + remainLenAfter += 1+2 += 3
// 2 3 2 1 2
// 2 3 2 1 2 3
// 1 2 3 *** 4..6 remainLenBefore = 4 - 4 + 1 = 1
// 2 1 2 3
// 1 2 1 2 3 2 3
// *.......* *** 0..4 sum += 1 + 2 = 3
// *...* *** 2..4 rla = 6 - 4 = 2, rlb = 2 - (0 + 1) = 1, sum += 1 + rla + rlb + rlb*rla += 6 = 9
// 1 3 5 2 7 5
// *...*
//
we derive the formula: \(sum += 1 + suffix + prefix + suffix*prefix\)
A more clever, but less understandable solution: is to count how many times we take a condition where we have a min
and a max
and each time add prefix
count. Basically, it is the same formula, but with a more clever way of computing. (It is like computing a combination sum by adding each time the counter to sum).
For the explicit solution, we take each interval, store positions of the min
and max
in a TreeSet
, then we must take poll those mins and maxes and consider each range separately:
// 3 2 3 2 1 2 1
// *.......*
// *...*
// 3 2 1 2 3 2 1
// *...*
// *...*
// *...*
// 3 2 1 2 1 2 3
// *...*
// *.......*
// *...*
// 3 2 1 2 3 3 3
// *...*
// *...*
// 3 2 2 2 2 2 1
// *...........*
// 1 1 1 1 1 1 1
// *.*
// *.*
// *.*
// *.*
// *.*
// *.*
For the tricky one solution, just see what other clever man already wrote on the leetcode site and hope you will not get the same problem in an interview.
Time complexity: \(O(nlog_2(n))\) -> \(O(n)\)
Space complexity: \(O(n)\) -> \(O(1)\)
28. Find the Index of the First Occurrence in a String medium
fun strStr(haystack: String, needle: String): Int {
// f(x) = a + 32 * f(x - 1)
// abc
// f(a) = a + 0
// f(ab) = b + 32 * (a + 0)
// f(abc) = c + 32 * (b + 32 * (a + 0))
//
// f(b) = b + 0
// f(bc) = c + 32 * (b + 0)
//
// f(abc) - f(bc) = 32^0*c + 32^1*b + 32^2*a - 32^0*c - 32^1*b = 32^2*a
// f(bc) = f(abc) - 32^2*a
var needleHash = 0L
needle.forEach { needleHash = it.toLong() + 32L * needleHash }
var currHash = 0L
var pow = 1L
repeat(needle.length) { pow *= 32L}
for (curr in 0..haystack.lastIndex) {
currHash = haystack[curr].toLong() + 32L * currHash
if (curr >= needle.length)
currHash -= pow * haystack[curr - needle.length].toLong()
if (curr >= needle.lastIndex
&& currHash == needleHash
&& haystack.substring(curr - needle.lastIndex, curr + 1) == needle)
return curr - needle.lastIndex
}
return -1
}
https://t.me/leetcode_daily_unstoppable/136
There is a rolling hash
technique: you can compute hash for a sliding window using O(1) additional time.
Consider the math behind it:
// f(x) = a + 32 * f(x - 1)
// abc
// f(a) = a + 0
// f(ab) = b + 32 * (a + 0)
// f(abc) = c + 32 * (b + 32 * (a + 0))
//
// f(b) = b + 0
// f(bc) = c + 32 * (b + 0)
//
// f(abc) - f(bc) = 32^0*c + 32^1*b + 32^2*a - 32^0*c - 32^1*b = 32^2*a
// f(bc) = f(abc) - 32^2*a
Basically, you can subtract char * 32^window_length
from the lower side of the sliding window.
443. String Compression medium
fun compress(chars: CharArray): Int {
var end = 0
var curr = 0
while (curr < chars.size) {
val c = chars[curr++]
var currCount = 1
while (curr < chars.size && c == chars[curr]) {
curr++
currCount++
}
chars[end++] = c
if (currCount > 1) currCount.toString().forEach { chars[end++] = it }
}
return end
}
https://t.me/leetcode_daily_unstoppable/135
You don’t need to split a number into groups of 9
’s.
The right way to convert number 123
into a string is to divide it by 10 each time, then reverse a part of the array.
toString
for simplicity.toString
. For this task it is a 4
912. Sort an Array medium
fun sortArray(nums: IntArray, from: Int = 0, to: Int = nums.lastIndex): IntArray {
if (from >= to) return nums
val mid = partition(nums, from, to)
sortArray(nums, from, mid - 1)
sortArray(nums, mid + 1, to)
return nums
}
fun IntArray.swap(i: Int, j: Int) { this[i] = this[j].also { this[j] = this[i] } }
fun partition(nums: IntArray, from: Int, to: Int): Int {
var border = nums[to]
var afterBorder = from
for (curr in from until to)
if (nums[curr] < border) nums.swap(curr, afterBorder++)
nums.swap(to, afterBorder)
return afterBorder
}
https://t.me/leetcode_daily_unstoppable/134
There are some tricks to optimize naive quicksort algorithm.
lo
, mid
and hi
elements for the pivot instead of just hi
tailrec
insertion sort
for a small partsLet’s just implement naive quicksort.
652. Find Duplicate Subtrees medium
fun findDuplicateSubtrees(root: TreeNode?): List<TreeNode?> {
val result = mutableListOf<TreeNode?>()
val hashes = HashSet<String>()
val added = HashSet<String>()
fun hashDFS(node: TreeNode): String {
return with(node) {
"[" + (left?.let { hashDFS(it) } ?: "*") +
"_" + `val` + "_" +
(right?.let { hashDFS(it) } ?: "*") + "]"
}.also {
if (!hashes.add(it) && added.add(it)) result.add(node)
}
}
if (root != null) hashDFS(root)
return result
}
https://t.me/leetcode_daily_unstoppable/132
We can traverse the tree and construct a hash of each node, then just compare nodes with equal hashes. Another way is to serialize the tree and compare that data.
Let’s use pre-order traversal and serialize each node into string, also add that into HashSet
and check for duplicates.
427. Construct Quad Tree medium
fun construct(grid: Array<IntArray>): Node? {
if (grid.isEmpty()) return null
fun dfs(xMin: Int, xMax: Int, yMin: Int, yMax: Int): Node? {
if (xMin == xMax) return Node(grid[yMin][xMin] == 1, true)
val xMid = xMin + (xMax - xMin) / 2
val yMid = yMin + (yMax - yMin) / 2
return Node(false, false).apply {
topLeft = dfs(xMin, xMid, yMin, yMid)
topRight = dfs(xMid + 1, xMax, yMin, yMid)
bottomLeft = dfs(xMin, xMid, yMid + 1, yMax)
bottomRight = dfs(xMid + 1, xMax, yMid + 1, yMax)
if (topLeft!!.isLeaf && topRight!!.isLeaf
&& bottomLeft!!.isLeaf && bottomRight!!.isLeaf) {
if (topLeft!!.`val` == topRight!!.`val`
&& topRight!!.`val` == bottomLeft!!.`val`
&& bottomLeft!!.`val` == bottomRight!!.`val`) {
`val` = topLeft!!.`val`
isLeaf = true
topLeft = null
topRight = null
bottomLeft = null
bottomRight = null
}
}
}
}
return dfs(0, grid[0].lastIndex, 0, grid.lastIndex)
}
https://t.me/leetcode_daily_unstoppable/131
We can construct the tree using DFS and divide and conquer technique. Build four nodes, then check if all of them are equal leafs.
72. Edit Distance hard
fun minDistance(word1: String, word2: String): Int {
val dp = Array(word1.length + 1) { IntArray(word2.length + 1) { -1 } }
fun dfs(i: Int, j: Int): Int {
return when {
dp[i][j] != -1 -> dp[i][j]
i == word1.length && j == word2.length -> 0
i == word1.length -> 1 + dfs(i, j+1)
j == word2.length -> 1 + dfs(i+1, j)
word1[i] == word2[j] -> dfs(i+1, j+1)
else -> {
val insert1Delete2 = 1 + dfs(i, j+1)
val insert2Delete1 = 1 + dfs(i+1, j)
val replace1Or2 = 1 + dfs(i+1, j+1)
val res = minOf(insert1Delete2, insert2Delete1, replace1Or2)
dp[i][j] = res
res
}
}
}
return dfs(0, 0)
}
https://t.me/leetcode_daily_unstoppable/130
Compare characters from each positions of the two strings. If they are equal, do nothing. If not, we can choose from three paths: removing, inserting or replacing. That will cost us one
point of operations. Then, do DFS and choose the minimum of the operations.
Do DFS and use array for memoizing the result.
121. Best Time to Buy and Sell Stock easy
fun maxProfit(prices: IntArray): Int {
var min = prices[0]
var profit = 0
prices.forEach {
if (it < min) min = it
profit = maxOf(profit, it - min)
}
return profit
}
https://t.me/leetcode_daily_unstoppable/129
Max profit will be the difference between max
and min
. One thing to note, the max
must follow after the min
.
max
candidate instead of managing the max
variable.
1675. Minimize Deviation in Array hard
fun minimumDeviation(nums: IntArray): Int {
var minDiff = Int.MAX_VALUE
with(TreeSet<Int>(nums.map { if (it % 2 == 0) it else it * 2 })) {
do {
val min = first()
val max = pollLast()
minDiff = minOf(minDiff, Math.abs(max - min))
add(max / 2)
} while (max % 2 == 0)
}
return minDiff
}
https://t.me/leetcode_daily_unstoppable/128
We can notice, that the answer is the difference between the min
and max
from some resulting set of numbers.
My first (wrong) intuition was, that we can use two heaps for minimums and maximums, and only can divide by two from the maximum, and multiply by two from the minimum heap. That quickly transformed into too many edge cases.
The correct and tricky intuition: we can multiply all the numbers by 2, and then we can safely begin to divide all the maximums until they can be divided.
Use TreeSet
to quickly access to the min
and max
elements.
502. IPO hard
fun findMaximizedCapital(k: Int, w: Int, profits: IntArray, capital: IntArray): Int {
val indices = Array(profits.size) { it }.apply { sortWith(compareBy( { capital[it] })) }
var money = w
with(PriorityQueue<Int>(profits.size, compareBy({ -profits[it] }))) {
var i = 0
repeat (k) {
while (i <= indices.lastIndex && money >= capital[indices[i]]) add(indices[i++])
if (isNotEmpty()) money += profits[poll()]
}
}
return money
}
https://t.me/leetcode_daily_unstoppable/127
My first (wrong) intuition: greedy add elements to the min-profit priority queue, then remove all low-profit elements from it, keeping essential items. It wasn’t working, and the solution became too verbose. Second intuition, after the hint: greedy add elements to the max-profit priority queue, then remove the maximum from it, which will be the best deal for the current money.
Sort items by increasing capital. Then, on each step, add all possible deals to the priority queue and take one best from it.
1011. Capacity To Ship Packages Within D Days medium
fun shipWithinDays(weights: IntArray, days: Int): Int {
var lo = weights.max()!!
var hi = weights.sum()!!
fun canShip(weight: Int): Boolean {
var curr = 0
var count = 1
weights.forEach {
curr += it
if (curr > weight) {
curr = it
count++
}
}
if (curr > weight) count++
return count <= days
}
var min = hi
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
val canShip = canShip(mid)
if (canShip) {
min = minOf(min, mid)
hi = mid - 1
} else lo = mid + 1
}
return min
}
https://t.me/leetcode_daily_unstoppable/126
Of all the possible capacities, there is an increasing possibility to carry the load. It may look like this: not possible
, not possible
, .., not possible
, possible
, possible
, .., possible
. We can binary search in that sorted space of possibilities.
To more robust binary search code:
lo
and hi
lo == hi
min = minOf(min, mid)
lo
and hi
540. Single Element in a Sorted Array medium
fun singleNonDuplicate(nums: IntArray): Int {
var lo = 0
var hi = nums.lastIndex
// 0 1 2 3 4
// 1 1 2 3 3
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
val prev = if (mid > 0) nums[mid-1] else -1
val next = if (mid < nums.lastIndex) nums[mid+1] else Int.MAX_VALUE
val curr = nums[mid]
if (prev < curr && curr < next) return curr
val oddPos = mid % 2 != 0
val isSingleOnTheLeft = oddPos && curr == next || !oddPos && curr == prev
if (isSingleOnTheLeft) hi = mid - 1 else lo = mid + 1
}
return -1
}
https://t.me/leetcode_daily_unstoppable/125
This problem is a brain-teaser until you notice that pairs are placed at even-odd
positions before the target and at odd-even
positions after.
Let’s write a binary search. For more robust code, consider:
lo
and hi
lo
or hi
35. Search Insert Position easy
fun searchInsert(nums: IntArray, target: Int): Int {
var lo = 0
var hi = nums.lastIndex
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (target == nums[mid]) return mid
if (target > nums[mid]) lo = mid + 1
else hi = mid - 1
}
return lo
}
https://t.me/leetcode_daily_unstoppable/124
Just do a binary search
For more robust code consider:
lo
and hi
lo == hi
mid + 1
or mid - 1
nums[mid] == target
lo
position - this is an insertion point103. Binary Tree Zigzag Level Order Traversal medium
fun zigzagLevelOrder(root: TreeNode?): List<List<Int>> = mutableListOf<List<Int>>().also { res ->
with(ArrayDeque<TreeNode>().apply { root?.let { add(it) } }) {
while (isNotEmpty()) {
val curr = LinkedList<Int>().apply { res.add(this) }
repeat(size) {
with(poll()) {
with(curr) { if (res.size % 2 == 0) addFirst(`val`) else addLast(`val`) }
left?.let { add(it) }
right?.let { add(it) }
}
}
}
}
}
https://t.me/leetcode_daily_unstoppable/123
Each BFS step gives us a level, which one we can reverse if needed.
fun invertTree(root: TreeNode?): TreeNode? =
root?.apply { left = invertTree(right).also { right = invertTree(left) } }
https://t.me/leetcode_daily_unstoppable/122
Walk tree with Depth-First Search and swap each left and right nodes.
Let’s write a recursive one-liner.
783. Minimum Distance Between BST Nodes easy
fun minDiffInBST(root: TreeNode?): Int {
var prev: TreeNode? = null
var curr = root
var minDiff = Int.MAX_VALUE
while (curr != null) {
if (curr.left == null) {
if (prev != null) minDiff = minOf(minDiff, Math.abs(curr.`val` - prev.`val`))
prev = curr
curr = curr.right
} else {
var right = curr.left!!
while (right.right != null && right.right != curr) right = right.right!!
if (right.right == curr) {
right.right = null
curr = curr.right
} else {
right.right = curr
val next = curr.left
curr.left = null
curr = next
}
}
}
return minDiff
}
https://t.me/leetcode_daily_unstoppable/121
Given that this is a Binary Search Tree, inorder
traversal will give us an increasing sequence of nodes. Minimum difference will be one of the adjacent nodes differences.
Let’s write Morris Traversal. Store current node at the rightmost end of the left children.
104. Maximum Depth of Binary Tree easy
fun maxDepth(root: TreeNode?): Int =
root?.run { 1 + maxOf(maxDepth(left), maxDepth(right)) } ?: 0
https://t.me/leetcode_daily_unstoppable/120
Do DFS and choose the maximum on each step.
Let’s write a one-liner.
989. Add to Array-Form of Integer easy
fun addToArrayForm(num: IntArray, k: Int): List<Int> {
var carry = 0
var i = num.lastIndex
var n = k
val res = LinkedList<Int>()
while (i >= 0 || n > 0 || carry > 0) {
val d1 = if (i >= 0) num[i--] else 0
val d2 = if (n > 0) n % 10 else 0
var d = d1 + d2 + carry
res.addFirst(d % 10)
carry = d / 10
n = n / 10
}
return res
}
https://t.me/leetcode_daily_unstoppable/119
Iterate from the end of the array and calculate sum of num % 10
, carry
and num[i]
.
67. Add Binary easy
fun addBinary(a: String, b: String): String = StringBuilder().apply {
var o = 0
var i = a.lastIndex
var j = b.lastIndex
while (i >= 0 || j >= 0 || o == 1) {
var num = o
o = 0
if (i >= 0 && a[i--] == '1') num++
if (j >= 0 && b[j--] == '1') num++
when (num) {
0 -> append('0')
1 -> append('1')
2 -> {
append('0')
o = 1
}
else -> {
append('1')
o = 1
}
}
}
}.reverse().toString()
https://t.me/leetcode_daily_unstoppable/118
Scan two strings from the end and calculate the result.
1523. Count Odd Numbers in an Interval Range easy
fun countOdds(low: Int, high: Int): Int {
if (low == high) return if (low % 2 == 0) 0 else 1
val lowOdd = low % 2 != 0
val highOdd = high % 2 != 0
val count = high - low + 1
return if (lowOdd && highOdd) {
1 + count / 2
} else if (lowOdd || highOdd) {
1 + (count - 1) / 2
} else {
1 + ((count - 2) / 2)
}
}
https://t.me/leetcode_daily_unstoppable/117
Count how many numbers in between, subtract even on the start and the end, then divide by 2.
2477. Minimum Fuel Cost to Report to the Capital medium
data class R(val cars: Long, val capacity: Int, val fuel: Long)
fun minimumFuelCost(roads: Array<IntArray>, seats: Int): Long {
val nodes = mutableMapOf<Int, MutableList<Int>>()
roads.forEach { (from, to) ->
nodes.getOrPut(from, { mutableListOf() }) += to
nodes.getOrPut(to, { mutableListOf() }) += from
}
fun dfs(curr: Int, parent: Int): R {
val children = nodes[curr]
if (children == null) return R(1L, seats - 1, 0L)
var fuel = 0L
var capacity = 0
var cars = 0L
children.filter { it != parent }.forEach {
val r = dfs(it, curr)
fuel += r.cars + r.fuel
capacity += r.capacity
cars += r.cars
}
// seat this passenger
if (capacity == 0) {
cars++
capacity = seats - 1
} else capacity--
// optimize cars
while (capacity - seats >= 0) {
capacity -= seats
cars--
}
return R(cars, capacity, fuel)
}
return dfs(0, 0).fuel
}
https://t.me/leetcode_daily_unstoppable/116
Let’s start from each leaf (node without children). We give one
car, seats-1
capacity and zero
fuel. When children cars arrive, each of them consume cars
capacity of the fuel. On the hub (node with children), we sat another one passenger, so capacity--
and we can optimize number of cars arrived, if total capacity
is more than one car seats
number.
Use DFS and data class for the result.
0..n
1129. Shortest Path with Alternating Colors medium
fun shortestAlternatingPaths(n: Int, redEdges: Array<IntArray>, blueEdges: Array<IntArray>): IntArray {
val edgesRed = mutableMapOf<Int, MutableList<Int>>()
val edgesBlue = mutableMapOf<Int, MutableList<Int>>()
redEdges.forEach { (from, to) ->
edgesRed.getOrPut(from, { mutableListOf() }).add(to)
}
blueEdges.forEach { (from, to) ->
edgesBlue.getOrPut(from, { mutableListOf() }).add(to)
}
val res = IntArray(n) { -1 }
val visited = hashSetOf<Pair<Int, Boolean>>()
var dist = 0
with(ArrayDeque<Pair<Int, Boolean>>()) {
add(0 to true)
add(0 to false)
visited.add(0 to true)
visited.add(0 to false)
while (isNotEmpty()) {
repeat(size) {
val (node, isRed) = poll()
if (res[node] == -1 || res[node] > dist) res[node] = dist
val edges = if (isRed) edgesRed else edgesBlue
edges[node]?.forEach {
if (visited.add(it to !isRed)) add(it to !isRed)
}
}
dist++
}
}
return res
}
https://t.me/leetcode_daily_unstoppable/115
We can calculate all the shortest distances in one pass BFS.
Start with two simultaneous points, one for red and one for blue. Keep track of the color.
1162. As Far from Land as Possible medium
fun maxDistance(grid: Array<IntArray>): Int = with(ArrayDeque<Pair<Int, Int>>()) {
val n = grid.size
val visited = hashSetOf<Pair<Int, Int>>()
fun tryAdd(x: Int, y: Int) {
if (x < 0 || y < 0 || x >= n || y >= n) return
(x to y).let { if (visited.add(it)) add(it) }
}
for (yStart in 0 until n)
for (xStart in 0 until n)
if (grid[yStart][xStart] == 1) tryAdd(xStart, yStart)
if (size == n*n) return -1
var dist = -1
while(isNotEmpty()) {
repeat(size) {
val (x, y) = poll()
tryAdd(x-1, y)
tryAdd(x, y-1)
tryAdd(x+1, y)
tryAdd(x, y+1)
}
dist++
}
dist
}
https://t.me/leetcode_daily_unstoppable/114
Let’s do a wave from each land and wait until all the last water cell reached. This cell will be the answer.
Add all land cells into BFS, then just run it.
fun distinctNames(ideas: Array<String>): Long {
// c -> offee
// d -> onuts
// t -> ime, offee
val prefToSuf = Array(27) { hashSetOf<String>() }
for (idea in ideas)
prefToSuf[idea[0].toInt() - 'a'.toInt()].add(idea.substring(1, idea.length))
var count = 0L
for (i in 0..26)
for (j in i + 1..26)
count += prefToSuf[i].count { !prefToSuf[j].contains(it) } * prefToSuf[j].count { ! prefToSuf[i].contains(it) }
return count * 2L
}
https://t.me/leetcode_daily_unstoppable/113
If we group ideas by the suffixes and consider only the unique elements, the result will be the intersection of the sizes of the groups. (To deduce this you must sit and draw, or have a big brain, or just use a hint)
Group and multiply. Don’t forget to remove repeating elements in each two groups.
45. Jump Game II medium
fun jump(nums: IntArray): Int {
if (nums.size <= 1) return 0
val stack = Stack<Int>()
// 0 1 2 3 4 5 6 7 8 9 1011121314
// 7 0 9 6 9 6 1 7 9 0 1 2 9 0 3
// *
// *
// * * *
// * * *
// * *
// *
// * * * * * * *
// * * * * * * * *
// * *
// * * * * * * *
// * * * * * * * * * *
// * * * * * * *
// * * * * * * * * * *
// *
// * * * * * * * *
// 3 4 3 2 5 4 3
// *
// * *
// * * *
// * * *
// * * * *
// * * * * *
// * * * *
// 0 1 2 3 4 5 6 7 8 9 1011
// 5 9 3 2 1 0 2 3 3 1 0 0
// *
// *
// * *
// * * * *
// * * * *
// * * *
// *
// * *
// * * *
// * * * *
// * * * * * * * * * *
// * * * * * *
for (pos in nums.lastIndex downTo 0) {
var canReach = minOf(pos + nums[pos], nums.lastIndex)
if (canReach == nums.lastIndex) stack.clear()
while (stack.size > 1 && stack.peek() <= canReach) {
val top = stack.pop()
if (stack.peek() > canReach) {
stack.push(top)
break
}
}
stack.push(pos)
}
return stack.size
}
https://t.me/leetcode_daily_unstoppable/112
The dynamic programming solution is trivial, and can be done in \(O(n^2)\). Greedy solution is to scan from back to front and keep only jumps that starts after the current max jump.
maxReach
904. Fruit Into Baskets medium
fun totalFruit(fruits: IntArray): Int {
if (fruits.size <= 2) return fruits.size
var type1 = fruits[fruits.lastIndex]
var type2 = fruits[fruits.lastIndex - 1]
var count = 2
var max = 2
var prevType = type2
var prevTypeCount = if (type1 == type2) 2 else 1
for (i in fruits.lastIndex - 2 downTo 0) {
val type = fruits[i]
if (type == type1 || type == type2 || type1 == type2) {
if (type1 == type2 && type != type1) type2 = type
if (type == prevType) prevTypeCount++
else prevTypeCount = 1
count++
} else {
count = prevTypeCount + 1
type2 = type
type1 = prevType
prevTypeCount = 1
}
max = maxOf(max, count)
prevType = type
}
return max
}
https://t.me/leetcode_daily_unstoppable/111
We can scan fruits linearly from the tail and keep only two types of fruits.
fun shuffle(nums: IntArray, n: Int): IntArray {
val arr = IntArray(nums.size)
var left = 0
var right = n
var i = 0
while (i < arr.lastIndex) {
arr[i++] = nums[left++]
arr[i++] = nums[right++]
}
return arr
}
https://t.me/leetcode_daily_unstoppable/110
Just do what is asked.
For simplicity, use two pointers for the source, and one for the destination.
438. Find All Anagrams in a String medium
fun findAnagrams(s: String, p: String): List<Int> {
val freq = IntArray(26) { 0 }
var nonZeros = 0
p.forEach {
val ind = it.toInt() - 'a'.toInt()
if (freq[ind] == 0) nonZeros++
freq[ind]--
}
val res = mutableListOf<Int>()
for (i in 0..s.lastIndex) {
val currInd = s[i].toInt() - 'a'.toInt()
if (freq[currInd] == 0) nonZeros++
freq[currInd]++
if (freq[currInd] == 0) nonZeros--
if (i >= p.length) {
val ind = s[i - p.length].toInt() - 'a'.toInt()
if (freq[ind] == 0) nonZeros++
freq[ind]--
if (freq[ind] == 0) nonZeros--
}
if (nonZeros == 0) res += i - p.length + 1
}
return res
}
https://t.me/leetcode_daily_unstoppable/109
We can count frequencies of p
and then scan s
to match them.
Time complexity: \(O(n)\)
567. Permutation in String medium
fun checkInclusion(s1: String, s2: String): Boolean {
val freq1 = IntArray(26) { 0 }
s1.forEach { freq1[it.toInt() - 'a'.toInt()]++ }
val freq2 = IntArray(26) { 0 }
for (i in 0..s2.lastIndex) {
freq2[s2[i].toInt() - 'a'.toInt()]++
if (i >= s1.length) freq2[s2[i - s1.length].toInt() - 'a'.toInt()]--
if (Arrays.equals(freq1, freq2)) return true
}
return false
}
https://t.me/leetcode_daily_unstoppable/108
We can count the chars frequencies in the s1
string and use the sliding window technique to count and compare char frequencies in the s2
.
6. Zigzag Conversion medium
fun convert(s: String, numRows: Int): String {
if (numRows <= 1) return s
// nr = 5
//
// 0 8 16 24
// 1 7 9 15 17 23 25
// 2 6 10 14 18 22 26 30
// 3 5 11 13 19 21 27 29
// 4 12 20 28
//
val indices = Array(numRows) { mutableListOf<Int>() }
var y = 0
var dy = 1
for (i in 0..s.lastIndex) {
indices[y].add(i)
if (i > 0 && (i % (numRows - 1)) == 0) dy = -dy
y += dy
}
return StringBuilder().apply {
indices.forEach { it.forEach { append(s[it]) } }
}.toString()
}
https://t.me/leetcode_daily_unstoppable/107
// nr = 5
//
// 0 8 16 24
// 1 7 9 15 17 23 25
// 2 6 10 14 18 22 26 30
// 3 5 11 13 19 21 27 29
// 4 12 20 28
//
We can just simulate zigzag.
Store simulation result in a [rowsNum][simulation indice]
- matrix, then build the result.
953. Verifying an Alien Dictionary easy
fun isAlienSorted(words: Array<String>, order: String): Boolean {
val orderChars = Array<Char>(26) { 'a' }
for (i in 0..25) orderChars[order[i].toInt() - 'a'.toInt()] = (i + 'a'.toInt()).toChar()
val arr = Array<String>(words.size) {
words[it].map { orderChars[it.toInt() - 'a'.toInt()] }.joinToString("")
}
val sorted = arr.sorted()
for (i in 0..arr.lastIndex) if (arr[i] != sorted[i]) return false
return true
}
https://t.me/leetcode_daily_unstoppable/106
For the example hello
and order hlabcdefgijkmnopqrstuvwxyz
we must translate like this: h
-> a
, l
-> b
, a
-> c
and so on. Then we can just use compareTo
to check the order.
Just translate and then sort and compare. (But we can also just scan linearly and compare).
1071. Greatest Common Divisor of Strings easy
fun gcdOfStrings(str1: String, str2: String): String {
if (str1 == "" || str2 == "") return ""
if (str1.length == str2.length) return if (str1 == str2) str1 else ""
fun gcd(a: Int, b: Int): Int {
return if (a == 0) b
else gcd(b % a, a)
}
val len = gcd(str1.length, str2.length)
for (i in 0..str1.lastIndex) if (str1[i] != str1[i % len]) return ""
for (i in 0..str2.lastIndex) if (str2[i] != str1[i % len]) return ""
return str1.substring(0, len)
}
https://t.me/leetcode_daily_unstoppable/105
Consider the following example: ababab
and abab
.
If we scan them linearly, we see, the common part is abab
.
Now, we need to check if the last part from the first abab_ab
is a part of the common part: ab
vs abab
.
This can be done recursively, and we come to the final consideration: "" vs "ab"
.
That all procedure give us the common divisor - ab
.
The actual hint is in the method’s name ;)
We can first find the length of the greatest common divisor, then just check both strings.
1626. Best Team With No Conflicts medium
fun bestTeamScore(scores: IntArray, ages: IntArray): Int {
val dp = Array(scores.size + 1) { IntArray(1001) { -1 }}
val indices = scores.indices.toMutableList()
indices.sortWith(compareBy( { scores[it] }, { ages[it] } ))
fun dfs(curr: Int, prevAge: Int): Int {
if (curr == scores.size) return 0
if (dp[curr][prevAge] != -1) return dp[curr][prevAge]
val ind = indices[curr]
val age = ages[ind]
val score = scores[ind]
val res = maxOf(
dfs(curr + 1, prevAge),
if (age < prevAge) 0 else score + dfs(curr + 1, age)
)
dp[curr][prevAge] = res
return res
}
return dfs(0, 0)
}
https://t.me/leetcode_daily_unstoppable/103
If we sort arrays by score
and age
, then every next item will be with score
bigger than previous.
If current age
is less than previous, then we can’t take it, as score
for current age
can’t be bigger than previous.
Let’s define dp[i][j]
is a maximum score for a team in i..n
sorted slice, and j
is a maximum age for that team.
We can use DFS to search all the possible teams and memorize the result in dp cache.
1137. N-th Tribonacci Number easy
fun tribonacci(n: Int): Int = if (n < 2) n else {
var t0 = 0
var t1 = 1
var t2 = 1
repeat(n - 2) {
t2 += (t0 + t1).also {
t0 = t1
t1 = t2
}
}
t2
}
https://t.me/leetcode_daily_unstoppable/102
Just do what is asked.
460. LFU Cache hard
class LFUCache(val capacity: Int) {
data class V(val key: Int, val value: Int, val freq: Int)
val mapKV = mutableMapOf<Int, V>()
val freqToAccessListOfK = TreeMap<Int, LinkedHashSet<V>>()
fun get(key: Int): Int {
val v = mapKV.remove(key)
if (v == null) return -1
increaseFreq(v, v.value)
return v.value
}
fun getAccessListForFreq(freq: Int) = freqToAccessListOfK.getOrPut(freq, { LinkedHashSet<V>() })
fun increaseFreq(v: V, value: Int) {
val oldFreq = v.freq
val newFreq = oldFreq + 1
val newV = V(v.key, value, newFreq)
mapKV[v.key] = newV
val accessList = getAccessListForFreq(oldFreq)
accessList.remove(v)
if (accessList.isEmpty()) freqToAccessListOfK.remove(oldFreq)
getAccessListForFreq(newFreq).add(newV)
}
fun put(key: Int, value: Int) {
if (capacity == 0) return
val oldV = mapKV[key]
if (oldV == null) {
if (mapKV.size == capacity) {
val lowestFreq = freqToAccessListOfK.firstKey()
val accessList = freqToAccessListOfK[lowestFreq]!!
val iterator = accessList.iterator()
val leastFreqV = iterator.next()
iterator.remove()
mapKV.remove(leastFreqV.key)
if (accessList.isEmpty()) freqToAccessListOfK.remove(lowestFreq)
}
val v = V(key, value, 1)
mapKV[key] = v
getAccessListForFreq(1).add(v)
} else {
increaseFreq(oldV, value)
}
}
}
https://t.me/leetcode_daily_unstoppable/101
Let’s store access-time list in a buckets divided by access-count frequencies. We can store each bucked in a TreeMap
, that will give us O(1) time to get the least frequent list. For the list we can use LinkedHashSet
, that can give us O(1) operations for remove
, removeFirst
and add
and will help to maintain access order.
increaseFreq
operation we are retrieving a random item from TreeMap, that increases time to O(log(F)), where F is a unique set of frequencies.k
we can have if there is a total number of N
operations? If sequence 1,2,3...k-1, k
is our unique set, then 1+2+3+...+(k-1)+k = N
. Or:
\(1+2+3+\cdots+k=\sum_{n=1}^{k}i = k(k-1)/2 = N\)
so,
\(k = \sqrt{N}\)
352. Data Stream as Disjoint Intervals hard
class SummaryRanges() {
data class Node(var start: Int, var end: Int, var next: Node? = null)
val root = Node(-1, -1)
fun mergeWithNext(n: Node?): Boolean {
if (n == null) return false
val curr = n
val next = n.next
if (next == null) return false
val nextNext = next.next
if (next.start - curr.end <= 1) {
curr.end = next.end
curr.next = nextNext
return true
}
return false
}
fun addNum(value: Int) {
var n = root
while (n.next != null && n.next!!.start < value) n = n.next!!
if (value in n.start..n.end) return
n.next = Node(value, value, n.next)
if (n != root && mergeWithNext(n))
mergeWithNext(n)
else
mergeWithNext(n.next)
}
fun getIntervals(): Array<IntArray> {
val list = mutableListOf<IntArray>()
var n = root.next
while (n != null) {
list.add(intArrayOf(n.start, n.end))
n = n.next
}
return list.toTypedArray()
}
}
https://t.me/leetcode_daily_unstoppable/100
In Kotlin there is no way around to avoid the O(n) time of an operation while building the result array. And there is no way to insert to the middle of the array in a less than O(n) time. So, the only way is to use the linked list, and to walk it linearly.
data class Trie(val ch: Char = '.', var isWord: Boolean = false) {
val next = Array<Trie?>(26) { null }
fun ind(c: Char) = c.toInt() - 'a'.toInt()
fun exists(c: Char) = next[ind(c)] != null
operator fun get(c: Char): Trie {
val ind = ind(c)
if (next[ind] == null) next[ind] = Trie(c)
return next[ind]!!
}
}
fun findAllConcatenatedWordsInADict(words: Array<String>): List<String> {
val trie = Trie()
words.forEach { word ->
var t = trie
word.forEach { t = t[it] }
t.isWord = true
}
val res = mutableListOf<String>()
words.forEach { word ->
var tries = ArrayDeque<Pair<Trie,Int>>()
tries.add(trie to 0)
for (c in word) {
repeat(tries.size) {
val (t, wc) = tries.poll()
if (t.exists(c)) {
val curr = t[c]
if (curr.isWord) tries.add(trie to (wc + 1))
tries.add(curr to wc)
}
}
}
if (tries.any { it.second > 1 && it.first === trie } ) res.add(word)
}
return res
}
https://t.me/leetcode_daily_unstoppable/99
When we scan a word we must know if current suffix is a word. Trie data structure will help.
787. Cheapest Flights Within K Stops medium
https://t.me/leetcode_daily_unstoppable/98
fun findCheapestPrice(n: Int, flights: Array<IntArray>, src: Int, dst: Int, k: Int): Int {
var dist = IntArray(n) { Int.MAX_VALUE }
dist[src] = 0
repeat(k + 1) {
val nextDist = dist.clone()
flights.forEach { (from, to, price) ->
if (dist[from] != Int.MAX_VALUE && dist[from] + price < nextDist[to])
nextDist[to] = dist[from] + price
}
dist = nextDist
}
return if (dist[dst] == Int.MAX_VALUE) -1 else dist[dst]
}
DFS and Dijkstra gives TLE.
As we need to find not just shortest path price, but only for k
steps, naive Bellman-Ford didn’t work.
Let’s define dist
, where dist[i]
- the shortest distance from src
node to i
-th node.
We initialize it with MAX_VALUE
, and dist[src]
is 0 by definition.
Next, we walk exactly k
steps, on each of them, trying to minimize price.
If we have known distance to node a
, dist[a] != MAX
.
And if there is a link to node b
with price(a,b)
, then we can optimize like this dist[b] = min(dist[b], dist[a] + price(a,b))
.
Because we’re starting from a single node dist[0]
, we will increase distance only once per iteration.
So, making k
iterations made our path exactly k
steps long.
k+1
, not just k
Space: O(kE), Time: O(k)
2359. Find Closest Node to Given Two Nodes medium
https://t.me/leetcode_daily_unstoppable/97
fun closestMeetingNode(edges: IntArray, node1: Int, node2: Int): Int {
val distances = mutableMapOf<Int, Int>()
var n = node1
var dist = 0
while (n != -1) {
if (distances.contains(n)) break
distances[n] = dist
n = edges[n]
dist++
}
n = node2
dist = 0
var min = Int.MAX_VALUE
var res = -1
while (n != -1) {
if (distances.contains(n)) {
val one = distances[n]!!
val max = maxOf(one, dist)
if (max < min || max == min && n < res) {
min = max
res = n
}
}
val tmp = edges[n]
edges[n] = -1
n = tmp
dist++
}
return res
}
We can walk with DFS and remember all distances, then compare them and choose those with minimum of maximums.
visited
set, or modify an inputSpace: O(n), Time: O(n)
909. Snakes and Ladders medium
https://t.me/leetcode_daily_unstoppable/96
fun snakesAndLadders(board: Array<IntArray>): Int {
fun col(pos: Int): Int {
return if (((pos/board.size) % 2) == 0)
(pos % board.size)
else
(board.lastIndex - (pos % board.size))
}
val last = board.size * board.size
var steps = 0
val visited = mutableSetOf<Int>()
with(ArrayDeque<Int>().apply { add(1) }) {
while (isNotEmpty() && steps <= last) {
repeat(size) {
var curr = poll()
val jump = board[board.lastIndex - (curr-1)/board.size][col(curr-1)]
if (jump != -1) curr = jump
if (curr == last) return steps
for (i in 1..6)
if (visited.add(curr + i) && curr + i <= last) add(curr + i)
}
steps++
}
}
return -1
}
In each step, we can choose the best outcome, so we need to travel all of them in the parallel and calculate steps number. This is a BFS.
We can avoid that strange order by iterating it and store into the linear array. Or just invent a formula for row and column by given index.
Space: O(n^2), Time: O(n^2), n is a grid size
https://t.me/leetcode_daily_unstoppable/95
fun findJudge(n: Int, trust: Array<IntArray>): Int {
val judges = mutableMapOf<Int, MutableSet<Int>>()
for (i in 1..n) judges[i] = mutableSetOf()
val notJudges = mutableSetOf<Int>()
trust.forEach { (from, judge) ->
judges[judge]!! += from
notJudges += from
}
judges.forEach { (judge, people) ->
if (people.size == n - 1
&& !people.contains(judge)
&& !notJudges.contains(judge))
return judge
}
return -1
}
We need to count how much trust have each judge and also exclude all judges that have trust in someone.
Space: O(max(N, T)), Time: O(max(N, T))
131. Palindrome Partitioning medium
https://t.me/leetcode_daily_unstoppable/93
fun partition(s: String): List<List<String>> {
val dp = Array(s.length) { BooleanArray(s.length) { false } }
for (from in s.lastIndex downTo 0)
for (to in from..s.lastIndex)
dp[from][to] = s[from] == s[to] && (from == to || from == to - 1 || dp[from+1][to-1])
val res = mutableListOf<List<String>>()
fun dfs(pos: Int, partition: MutableList<String>) {
if (pos == s.length) res += partition.toList()
for (i in pos..s.lastIndex)
if (dp[pos][i]) {
partition += s.substring(pos, i+1)
dfs(i+1, partition)
partition.removeAt(partition.lastIndex)
}
}
dfs(0, mutableListOf())
return res
}
First, we need to be able to quickly tell if some range a..b
is a palindrome.
Let’s dp[a][b]
indicate that range a..b
is a palindrome.
Then the following is true: dp[a][b] = s[a] == s[b] && dp[a+1][b-1]
, also two corner cases, when a == b
and a == b-1
.
For example, “a” and “aa”.
dp
for precomputing palindrome range answers.Space: O(2^N), Time: O(2^N)
93. Restore IP Addresses medium
https://t.me/leetcode_daily_unstoppable/92
fun restoreIpAddresses(s: String): List<String> {
val res = mutableSetOf<String>()
fun dfs(pos: Int, nums: MutableList<Int>) {
if (pos == s.length || nums.size > 4) {
if (nums.size == 4) res += nums.joinToString(".")
return
}
var n = 0
for (i in pos..s.lastIndex) {
n = n*10 + s[i].toInt() - '0'.toInt()
if (n > 255) break
nums += n
dfs(i + 1, nums)
nums.removeAt(nums.lastIndex)
if (n == 0) break
}
}
dfs(0, mutableListOf())
return res.toList()
}
So, the size of the problem is small. We can do full DFS. At every step, choose either take a number or split. Add to the solution if the result is good.
Some optimizations:
Space: O(2^N), Time: O(2^N)
491. Non-decreasing Subsequences medium
https://t.me/leetcode_daily_unstoppable/91
fun findSubsequences(nums: IntArray): List<List<Int>> {
val res = mutableSetOf<List<Int>>()
fun dfs(pos: Int, currList: MutableList<Int>) {
if (currList.size > 1) res += currList.toList()
if (pos == nums.size) return
val currNum = nums[pos]
//not add
dfs(pos + 1, currList)
//to add
if (currList.isEmpty() || currList.last()!! <= currNum) {
currList += currNum
dfs(pos + 1, currList)
currList.removeAt(currList.lastIndex)
}
}
dfs(0, mutableListOf())
return res.toList()
}
Notice the size of the problem, we can do a brute force search for all solutions. Also, we only need to store the unique results, so we can store them in a set.
Space: O(2^N) to store the result, Time: O(2^N) for each value we have two choices, and we can build a binary tree of choices with the 2^n number of elements.
974. Subarray Sums Divisible by K medium
https://t.me/leetcode_daily_unstoppable/90
fun subarraysDivByK(nums: IntArray, k: Int): Int {
// 4 5 0 -2 -3 1 k=5 count
// 4 4:1 0
// 9 4:2 +1
// 9 4:3 +2
// 7 2:1
// 4 4:4 +3
// 5 0:2 +1
// 2 -2 2 -4 k=6
// 2 2:1
// 0 0:2 +1
// 2 2:2 +1
// -2 2:3 +2
// 1 2 13 -2 3 k=7
// 1
// 3
// 16
// 14
// 17 (17-1*7= 10, 17-2*7=3, 17-3*7=-4, 17-4*7 = -11)
val freq = mutableMapOf<Int, Int>()
freq[0] = 1
var sum = 0
var res = 0
nums.forEach {
sum += it
var ind = (sum % k)
if (ind < 0) ind += k
val currFreq = freq[ind] ?: 0
res += currFreq
freq[ind] = 1 + currFreq
}
return res
}
We need to calculate a running sum.
For every current sum, we need to find any subsumes that are divisible by k, so sum[i]: (sum[i] - sum[any prev]) % k == 0
.
Or, sum[i] % k == sum[any prev] % k
.
Now, we need to store all sum[i] % k
values, count them and add to result.
We can save frequency in a map, or in an array [0..k], because all the values are from that range.
Space: O(N), Time: O(N)
918. Maximum Sum Circular Subarray medium
https://t.me/leetcode_daily_unstoppable/89
fun maxSubarraySumCircular(nums: IntArray): Int {
var maxEndingHere = 0
var maxEndingHereNegative = 0
var maxSoFar = Int.MIN_VALUE
var total = nums.sum()
nums.forEach {
maxEndingHere += it
maxEndingHereNegative += -it
maxSoFar = maxOf(maxSoFar, maxEndingHere, if (total == -maxEndingHereNegative) Int.MIN_VALUE else total+maxEndingHereNegative)
if (maxEndingHere < 0) {
maxEndingHere = 0
}
if (maxEndingHereNegative < 0) {
maxEndingHereNegative = 0
}
}
return maxSoFar
}
Simple Kadane’s Algorithm didn’t work when we need to keep a window of particular size. One idea is to invert the problem and find the minimum sum and subtract it from the total.
One corner case:
Space: O(1), Time: O(N)
926. Flip String to Monotone Increasing medium
https://t.me/leetcode_daily_unstoppable/88
fun minFlipsMonoIncr(s: String): Int {
// 010110 dp0 dp1 min
// 0 0 0 0
// 1 1 0 1
// 0 1 1 1
// 1 2 1 1
// 1 3 1 1
// 0 3 2 2
var dp0 = 0
var dp1 = 0
for (i in 0..s.lastIndex) {
dp0 = if (s[i] == '0') dp0 else 1 + dp0
dp1 = if (s[i] == '1') dp1 else 1 + dp1
if (dp0 <= dp1) dp1 = dp0
}
return minOf(dp0, dp1)
}
We can propose the following rule: let’s define dp0[i]
is a min count of flips from 1
to 0
in the 0..i
interval.
Let’s also define dp1[i]
is a min count of flips from 0
to 1
in the 0..i
interval.
We observe that dp0[i] = dp0[i-1] + (flip one to zero? 1 : 0)
and dp1[i] = dp1[i-1] + (flip zero to one? 1 : 0)
.
One special case: if on the interval 0..i
one-to-zero flips count is less than zero-to-one then we prefer to flip everything to zeros, and dp1[i]
in that case becomes dp0[i]
.
Just write down what is described above.
Space: O(1), Time: O(N)
57. Insert Interval medium
https://t.me/leetcode_daily_unstoppable/87
fun insert(intervals: Array<IntArray>, newInterval: IntArray): Array<IntArray> {
val res = mutableListOf<IntArray>()
var added = false
fun add() {
if (!added) {
added = true
if (res.isNotEmpty() && res.last()[1] >= newInterval[0]) {
res.last()[1] = maxOf(res.last()[1], newInterval[1])
} else res += newInterval
}
}
intervals.forEach { interval ->
if (newInterval[0] <= interval[0]) add()
if (res.isNotEmpty() && res.last()[1] >= interval[0]) {
res.last()[1] = maxOf(res.last()[1], interval[1])
} else res += interval
}
add()
return res.toTypedArray()
}
There is no magic, just be careful with corner cases.
Make another list, and iterate interval, merging them and adding at the same time.
newInterval
if it is not added after iteration.Space: O(N), Time: O(N)
2421. Number of Good Paths hard
https://t.me/leetcode_daily_unstoppable/86
fun numberOfGoodPaths(vals: IntArray, edges: Array<IntArray>): Int {
if (edges.size == 0) return vals.size
edges.sortWith(compareBy( { maxOf( vals[it[0]], vals[it[1]] ) } ))
val uf = IntArray(vals.size) { it }
val freq = Array(vals.size) { mutableMapOf(vals[it] to 1) }
fun find(x: Int): Int {
var p = x
while (uf[p] != p) p = uf[p]
uf[x] = p
return p
}
fun union(a: Int, b: Int): Int {
val rootA = find(a)
val rootB = find(b)
if (rootA == rootB) return 0
uf[rootA] = rootB
val vMax = maxOf(vals[a], vals[b]) // if we connect tree [1-3] to tree [2-1], only `3` matters
val countA = freq[rootA][vMax] ?:0
val countB = freq[rootB][vMax] ?:0
freq[rootB][vMax] = countA + countB
return countA * countB
}
return edges.map { union(it[0], it[1])}.sum()!! + vals.size
}
The naive solution with single DFS and merging frequency maps gives TLE. Now, use hint, and they tell you to sort edges and use Union-Find :) The idea is to connect subtrees, but walk them from smallest to the largest of value. When we connect two subtrees, we look at the maximum of each subtree. The minimum values don’t matter because the path will break at the maximums by definition of the problem.
Use IntArray for Union-Find, and also keep frequencies maps for each root.
Space: O(NlogN), Time: O(N)
1061. Lexicographically Smallest Equivalent String medium
https://t.me/leetcode_daily_unstoppable/85
fun smallestEquivalentString(s1: String, s2: String, baseStr: String): String {
val uf = IntArray(27) { it }
fun find(ca: Char): Int {
val a = ca.toInt() - 'a'.toInt()
var x = a
while (uf[x] != x) x = uf[x]
uf[a] = x
return x
}
fun union(a: Char, b: Char) {
val rootA = find(a)
val rootB = find(b)
if (rootA != rootB) {
val max = maxOf(rootA, rootB)
val min = minOf(rootA, rootB)
uf[max] = min
}
}
for (i in 0..s1.lastIndex) union(s1[i], s2[i])
return baseStr.map { (find(it) + 'a'.toInt()).toChar() }.joinToString("")
}
We need to find connected groups, the best way is to use the Union-Find.
Iterate over strings and connect each of their chars.
Space: O(N) for storing a result, Time: O(N)
2246. Longest Path With Different Adjacent Characters hard
https://t.me/leetcode_daily_unstoppable/84
fun longestPath(parent: IntArray, s: String): Int {
val graph = mutableMapOf<Int, MutableList<Int>>()
for (i in 1..parent.lastIndex)
if (s[i] != s[parent[i]]) graph.getOrPut(parent[i], { mutableListOf() }) += i
var maxLen = 0
fun dfs(curr: Int): Int {
parent[curr] = curr
var max1 = 0
var max2 = 0
graph[curr]?.forEach {
val childLen = dfs(it)
if (childLen > max1) {
max2 = max1
max1 = childLen
} else if (childLen > max2) max2 = childLen
}
val childChainLen = 1 + (max1 + max2)
val childMax = 1 + max1
maxLen = maxOf(maxLen, childMax, childChainLen)
return childMax
}
for (i in 0..parent.lastIndex) if (parent[i] != i) dfs(i)
return maxLen
}
Longest path is a maximum sum of the two longest paths of the current node.
Let’s build a graph and then recursively iterate it by DFS. We need to find two largest results from the children DFS calls.
parent[i] == i
to store a visited
stateSpace: O(N), Time: O(N), in DFS we visit each node only once.
1519. Number of Nodes in the Sub-Tree With the Same Label medium
https://t.me/leetcode_daily_unstoppable/83
fun countSubTrees(n: Int, edges: Array<IntArray>, labels: String): IntArray {
val graph = mutableMapOf<Int, MutableList<Int>>()
edges.forEach { (from, to) ->
graph.getOrPut(from, { mutableListOf() }) += to
graph.getOrPut(to, { mutableListOf() }) += from
}
val answer = IntArray(n) { 0 }
fun dfs(node: Int, parent: Int, counts: IntArray) {
val index = labels[node].toInt() - 'a'.toInt()
val countParents = counts[index]
counts[index]++
graph[node]?.forEach {
if (it != parent) {
dfs(it, node, counts)
}
}
answer[node] = counts[index] - countParents
}
dfs(0, 0, IntArray(27) { 0 })
return answer
}
First, we need to build a graph. Next, just do DFS and count all 'a'..'z'
frequencies in the current subtree.
For building a graph let’s use a map, and for DFS let’s use a recursion.
parent
node instead of the visited setcount before
Space: O(N), Time: O(N)
1443. Minimum Time to Collect All Apples in a Tree medium
https://t.me/leetcode_daily_unstoppable/82
fun minTime(n: Int, edges: Array<IntArray>, hasApple: List<Boolean>): Int {
val graph = mutableMapOf<Int, MutableList<Int>>()
edges.forEach { (from, to) ->
graph.getOrPut(to, { mutableListOf() }) += from
graph.getOrPut(from, { mutableListOf() }) += to
}
val queue = ArrayDeque<Int>()
queue.add(0)
val parents = IntArray(n+1) { it }
while (queue.isNotEmpty()) {
val node = queue.poll()
graph[node]?.forEach {
if (parents[it] == it && it != 0) {
parents[it] = node
queue.add(it)
}
}
}
var time = 0
hasApple.forEachIndexed { i, has ->
if (has) {
var node = i
while (node != parents[node]) {
val parent = parents[node]
parents[node] = node
node = parent
time++
}
}
}
return time * 2
}
We need to count all paths from apples to 0-node and don’t count already walked path.
edges
array. We need to build the tree first.First, build the tree, let it be a parents
array, where parent[i]
is a parent of the i
.
Walk graph with DFS and write the parents.
Next, walk hasApple
list and for each apple count parents until reach node 0
or already visited node.
To mark a node as visited, make it the parent of itself.
Space: O(N), Time: O(N)
100. Same Tree easy
https://t.me/leetcode_daily_unstoppable/81
fun isSameTree(p: TreeNode?, q: TreeNode?): Boolean = p == null && q == null ||
p?.`val` == q?.`val` && isSameTree(p?.left, q?.left) && isSameTree(p?.right, q?.right)
Check for the current node and repeat for the children. Let’s write one-liner
Space: O(logN) for stack, Time: O(n)
144. Binary Tree Preorder Traversal easy
https://t.me/leetcode_daily_unstoppable/80
class Solution {
fun preorderTraversal(root: TreeNode?): List<Int> {
val res = mutableListOf<Int>()
var node = root
while(node != null) {
res.add(node.`val`)
if (node.left != null) {
if (node.right != null) {
var rightmost = node.left!!
while (rightmost.right != null) rightmost = rightmost.right
rightmost.right = node.right
}
node = node.left
} else if (node.right != null) node = node.right
else node = null
}
return res
}
fun preorderTraversalStack(root: TreeNode?): List<Int> {
val res = mutableListOf<Int>()
var node = root
val rightStack = ArrayDeque<TreeNode>()
while(node != null) {
res.add(node.`val`)
if (node.left != null) {
if (node.right != null) {
rightStack.addLast(node.right!!) // <-- this step can be replaced with Morris
// traversal.
}
node = node.left
} else if (node.right != null) node = node.right
else if (rightStack.isNotEmpty()) node = rightStack.removeLast()
else node = null
}
return res
}
fun preorderTraversalRec(root: TreeNode?): List<Int> = mutableListOf<Int>().apply {
root?.let {
add(it.`val`)
addAll(preorderTraversal(it.left))
addAll(preorderTraversal(it.right))
}
}
}
Recursive solution is a trivial. For stack solution, we need to remember each right
node. Morris’ solution use the tree modification to save each right
node in the rightmost end of the left subtree.
Let’s implement them all.
Space: O(logN) for stack, O(1) for Morris’, Time: O(n)
149. Max Points on a Line hard
https://t.me/leetcode_daily_unstoppable/79
fun maxPoints(points: Array<IntArray>): Int {
if (points.size == 1) return 1
val pointsByTan = mutableMapOf<Pair<Double, Double>, HashSet<Int>>()
fun gcd(a: Int, b: Int): Int {
return if (b == 0) a else gcd(b, a%b)
}
for (p1Ind in points.indices) {
val p1 = points[p1Ind]
for (p2Ind in (p1Ind+1)..points.lastIndex) {
val p2 = points[p2Ind]
val x1 = p1[0]
val x2 = p2[0]
val y1 = p1[1]
val y2 = p2[1]
var dy = y2 - y1
var dx = x2 - x1
val greatestCommonDivider = gcd(dx, dy)
dy /= greatestCommonDivider
dx /= greatestCommonDivider
val tan = dy/dx.toDouble()
val b = if (dx == 0) x1.toDouble() else (x2*y1 - x1*y2 )/(x2-x1).toDouble()
val line = pointsByTan.getOrPut(tan to b, { HashSet() })
line.add(p1Ind)
line.add(p2Ind)
}
}
return pointsByTan.values.maxBy { it.size }?.size?:0
}
Just do the linear algebra to find all the lines through each pair of points.
Store slope
and b
coeff in the hashmap. Also, compute gcd
to find precise slope. In this case it works for double
precision slope, but for bigger numbers we need to store dy
and dx
separately in Int
precision.
Space: O(n^2), Time: O(n^2)
134. Gas Station medium
https://t.me/leetcode_daily_unstoppable/78
fun canCompleteCircuit(gas: IntArray, cost: IntArray): Int {
var sum = 0
var minSum = gas[0]
var ind = -1
for (i in 0..gas.lastIndex) {
sum += gas[i] - cost[i]
if (sum < minSum) {
minSum = sum
ind = (i+1) % gas.size
}
}
return if (sum < 0) -1 else ind
}
We can start after the station with the minimum decrease
in gasoline.
Calculate running gasoline volume and find the minimum of it. If the total net gasoline is negative, there is no answer.
Space: O(1), Time: O(N)
1833. Maximum Ice Cream Bars medium
https://t.me/leetcode_daily_unstoppable/77
fun maxIceCream(costs: IntArray, coins: Int): Int {
costs.sort()
var coinsRemain = coins
var iceCreamCount = 0
for (i in 0..costs.lastIndex) {
coinsRemain -= costs[i]
if (coinsRemain < 0) break
iceCreamCount++
}
return iceCreamCount
}
The maximum ice creams
would be if we take as many minimum costs
as possible
Sort the costs
array, then greedily iterate it and buy ice creams until all the coins are spent.
Space: O(1), Time: O(NlogN) (there is also O(N) solution based on count sort)
452. Minimum Number of Arrows to Burst Balloons medium
https://t.me/leetcode_daily_unstoppable/75
fun findMinArrowShots(points: Array<IntArray>): Int {
if (points.isEmpty()) return 0
if (points.size == 1) return 1
Arrays.sort(points, Comparator<IntArray> { a, b ->
if (a[0] == b[0]) a[1].compareTo(b[1]) else a[0].compareTo(b[0]) })
var arrows = 1
var arrX = points[0][0]
var minEnd = points[0][1]
for (i in 1..points.lastIndex) {
val (start, end) = points[i]
if (minEnd < start) {
arrows++
minEnd = end
}
if (end < minEnd) minEnd = end
arrX = start
}
return arrows
}
The optimal strategy to achieve the minimum number of arrows is to find the maximum overlapping intervals. For this task, we can sort the points by their start
and end
coordinates and use line sweep technique. Overlapping intervals are separate if their minEnd
is less than start
of the next interval. minEnd
- the minimum of the end
’s of the overlapping intervals.
Let’s move the arrow to each start
interval and fire a new arrow if this start
is greater than minEnd
.
compareTo
instead of subtractionSpace: O(1), Time: O(NlogN)
2244. Minimum Rounds to Complete All Tasks medium
https://t.me/leetcode_daily_unstoppable/74
fun minimumRounds(tasks: IntArray): Int {
val counts = mutableMapOf<Int, Int>()
tasks.forEach { counts[it] = 1 + counts.getOrDefault(it, 0)}
var round = 0
val cache = mutableMapOf<Int, Int>()
fun fromCount(count: Int): Int {
if (count == 0) return 0
if (count < 0 || count == 1) return -1
return if (count % 3 == 0) {
count/3
} else {
cache.getOrPut(count, {
var v = fromCount(count - 3)
if (v == -1) v = fromCount(count - 2)
if (v == -1) -1 else 1 + v
})
}
}
counts.values.forEach {
val rounds = fromCount(it)
if (rounds == -1) return -1
round += rounds
}
return round
}
For the optimal solution, we must take as many 3’s of tasks as possible, then take 2’s in any order.
First, we need to count how many tasks of each type there are. Next, we need to calculate the optimal rounds
for the current tasks type count. There is a math solution, but ultimately we just can do DFS
Space: O(N), Time: O(N), counts range is always less than N
944. Delete Columns to Make Sorted easy
https://t.me/leetcode_daily_unstoppable/73
fun minDeletionSize(strs: Array<String>): Int =
(0..strs[0].lastIndex).asSequence().count { col ->
(1..strs.lastIndex).asSequence().any { strs[it][col] < strs[it-1][col] }
}
Just do what is asked.
We can use Kotlin’s sequence
api.
Space: O(1), Time: O(wN)
520. Detect Capital easy
https://t.me/leetcode_daily_unstoppable/72
fun detectCapitalUse(word: String): Boolean =
word.all { Character.isUpperCase(it) } ||
word.all { Character.isLowerCase(it) } ||
Character.isUpperCase(word[0]) && word.drop(1).all { Character.isLowerCase(it) }
We can do this optimally by checking the first character and then checking all the other characters in a single pass. Or we can write a more understandable code that directly translates from the problem description. Let’s write one-liner.
Space: O(1), Time: O(N)
290. Word Pattern easy
https://t.me/leetcode_daily_unstoppable/71
fun wordPattern(pattern: String, s: String): Boolean {
val charToWord = Array<String>(27) { "" }
val words = s.split(" ")
if (words.size != pattern.length) return false
words.forEachIndexed { i, w ->
val cInd = pattern[i].toInt() - 'a'.toInt()
if (charToWord[cInd] == "") {
charToWord[cInd] = w
} else if (charToWord[cInd] != w) return false
}
charToWord.sort()
for (i in 1..26)
if (charToWord[i] != "" && charToWord[i] == charToWord[i-1])
return false
return true
}
Each word must be in 1 to 1 relation with each character in the pattern. We can check this rule.
Use string[27]
array for char -> word
relation and also check each char have a unique word assigned.
Space: O(N), Time: O(N)
https://t.me/leetcode_daily_unstoppable/69
fun uniquePathsIII(grid: Array<IntArray>): Int {
var countEmpty = 1
var startY = 0
var startX = 0
for (y in 0..grid.lastIndex) {
for (x in 0..grid[0].lastIndex) {
when(grid[y][x]) {
0 -> countEmpty++
1 -> { startY = y; startX = x}
else -> Unit
}
}
}
fun dfs(y: Int, x: Int): Int {
if (y < 0 || x < 0 || y >= grid.size || x >= grid[0].size) return 0
val curr = grid[y][x]
if (curr == 2) return if (countEmpty == 0) 1 else 0
if (curr == -1) return 0
grid[y][x] = -1
countEmpty--
val res = dfs(y-1, x) + dfs(y, x-1) + dfs(y+1, x) + dfs(y, x+1)
countEmpty++
grid[y][x] = curr
return res
}
return dfs(startY, startX)
}
There is only 20x20
cells, we can brute-force the solution.
We can use DFS, and count how many empty cells passed. To avoid visiting cells twice, modify grid
cell and then modify it back, like backtracking.
Space: O(1), Time: O(4^N)
797. All Paths From Source to Target medium
https://t.me/leetcode_daily_unstoppable/68
fun allPathsSourceTarget(graph: Array<IntArray>): List<List<Int>> {
val res = mutableListOf<List<Int>>()
val currPath = mutableListOf<Int>()
fun dfs(curr: Int) {
currPath += curr
if (curr == graph.lastIndex) res += currPath.toList()
graph[curr].forEach { dfs(it) }
currPath.removeAt(currPath.lastIndex)
}
dfs(0)
return res
}
We must find all the paths, so there is no shortcuts to the visiting all of them. One technique is backtracking - reuse existing visited list of nodes.
Space: O(VE), Time: O(VE)
1834. Single-Threaded CPU medium
https://t.me/leetcode_daily_unstoppable/67
fun getOrder(tasks: Array<IntArray>): IntArray {
val pqSource = PriorityQueue<Int>(compareBy(
{ tasks[it][0] },
{ tasks[it][1] },
{ it }
))
(0..tasks.lastIndex).forEach { pqSource.add(it) }
val pq = PriorityQueue<Int>(compareBy(
{ tasks[it][1] },
{ it }
))
val res = IntArray(tasks.size) { 0 }
var time = 1
for(resPos in 0..tasks.lastIndex) {
while (pqSource.isNotEmpty() && tasks[pqSource.peek()][0] <= time) {
pq.add(pqSource.poll())
}
if (pq.isEmpty()) {
//idle
pq.add(pqSource.poll())
time = tasks[pq.peek()][0]
}
//take task
val taskInd = pq.poll()
val task = tasks[taskInd]
time += task[1]
res[resPos] = taskInd
}
return res
}
First we need to sort tasks by their availability (and other rules), then take tasks one by one and add them to another sorted set/heap where their start time doesn’t matter, but running time and order does. When we take the task from the heap, we increase the time and fill in the heap.
Space: O(n), Time: O(nlogn)
1962. Remove Stones to Minimize the Total medium
https://t.me/leetcode_daily_unstoppable/66
fun minStoneSum(piles: IntArray, k: Int): Int {
val pq = PriorityQueue<Int>()
var sum = 0
piles.forEach {
sum += it
pq.add(-it)
}
for (i in 1..k) {
if (pq.isEmpty()) break
val max = -pq.poll()
if (max == 0) break
val newVal = Math.round(max/2.0).toInt()
sum -= max - newVal
pq.add(-newVal)
}
return sum
}
By the problem definition, intuitively the best strategy is to reduce the maximum each time.
Use PriorityQueue
to keep track of the maximum value and update it dynamically.
sum
and update it each time.Space: O(n), Time: O(nlogn)
2279. Maximum Bags With Full Capacity of Rocks medium
https://t.me/leetcode_daily_unstoppable/65
fun maximumBags(capacity: IntArray, rocks: IntArray, additionalRocks: Int): Int {
val inds = Array<Int>(capacity.size) { it }
inds.sortWith(Comparator { a,b -> capacity[a]-rocks[a] - capacity[b] + rocks[b] })
var rocksRemain = additionalRocks
var countFull = 0
for (i in 0..inds.lastIndex) {
val toAdd = capacity[inds[i]] - rocks[inds[i]]
if (toAdd > rocksRemain) break
rocksRemain -= toAdd
countFull++
}
return countFull
}
We can logically deduce that the optimal solution is to take first bags with the smallest empty space.
Make an array of indexes and sort it by difference between capacity
and rocks
. Then just simulate rocks addition to each bug from the smallest empty space to the largest.
Space: O(n), Time: O(nlogn)
55. Jump Game medium
https://t.me/leetcode_daily_unstoppable/64
fun canJump(nums: IntArray): Boolean {
var minInd = nums.lastIndex
for (i in nums.lastIndex - 1 downTo 0) {
if (nums[i] + i >= minInd) minInd = i
}
return minInd == 0
}
For any position i
we can reach the end if there is a minInd
such that nums[i] + i >= minInd
and minInd
is a known to be reaching the end.
We can run from the end and update minInd
- minimum index reaching the end.
Space: O(1), Time: O(N)
2389. Longest Subsequence With Limited Sum easy
https://t.me/leetcode_daily_unstoppable/63
fun answerQueries(nums: IntArray, queries: IntArray): IntArray {
nums.sort()
for (i in 1..nums.lastIndex) nums[i] += nums[i-1]
return IntArray(queries.size) {
val ind = nums.binarySearch(queries[it])
if (ind < 0) -ind-1 else ind+1
}
}
We can logically deduce that for the maximum number of arguments we need to take as much as possible items from the smallest to the largest.
We can sort items. Then pre-compute sums[i] = sum from [0..i]
. Then use binary search target sum in sums. Also, can modify nums
but that’s may be not necessary.
Space: O(N), Time: O(NlogN)
790. Domino and Tromino Tiling medium
https://t.me/leetcode_daily_unstoppable/62
fun numTilings(n: Int): Int {
val cache = Array<Array<Array<Long>>>(n) { Array(2) { Array(2) { -1L }}}
fun dfs(pos: Int, topFree: Int, bottomFree: Int): Long {
return when {
pos > n -> 0L
pos == n -> if (topFree==1 && bottomFree==1) 1L else 0L
else -> {
var count = cache[pos][topFree][bottomFree]
if (count == -1L) {
count = 0L
when {
topFree==1 && bottomFree==1 -> {
count += dfs(pos+1, 1, 1) // vertical
count += dfs(pos+1, 0, 0) // horizontal
count += dfs(pos+1, 1, 0) // tromino top
count += dfs(pos+1, 0, 1) // tromino bottom
}
topFree==1 -> {
count += dfs(pos+1, 0, 0) // tromino
count += dfs(pos+1, 1, 0) // horizontal
}
bottomFree==1 -> {
count += dfs(pos+1, 0, 0) // tromino
count += dfs(pos+1, 0, 1) // horizontal
}
else -> {
count += dfs(pos+1, 1, 1) // skip
}
}
count = count % 1_000_000_007L
}
cache[pos][topFree][bottomFree] = count
count
}
}
}
return dfs(0, 1, 1).toInt()
}
We can walk the board horizontally and monitor free cells. On each step, we can choose what figure to place. When end reached and there are no free cells, consider that a successful combination. Result depends only on the current position and on the top-bottom cell combination.* just do dfs+memo
Space: O(N), Time: O(N) - we only visit each column 3 times
309. Best Time to Buy and Sell Stock with Cooldown medium
https://t.me/leetcode_daily_unstoppable/61
data class K(val a:Int, val b: Boolean, val c:Boolean)
fun maxProfit(prices: IntArray): Int {
val cache = mutableMapOf<K, Int>()
fun dfs(pos: Int, canSell: Boolean, canBuy: Boolean): Int {
return if (pos == prices.size) 0
else cache.getOrPut(K(pos, canSell, canBuy), {
val profitSkip = dfs(pos+1, canSell, !canSell)
val profitSell = if (canSell) {prices[pos] + dfs(pos+1, false, false)} else 0
val profitBuy = if (canBuy) {-prices[pos] + dfs(pos+1, true, false)} else 0
maxOf(profitSkip, profitBuy, profitSell)
})
}
return dfs(0, false, true)
}
Progress from dfs solution to memo. DFS solution - just choose what to do in this step, go next, then compare results and peek max.
Space: O(N), Time: O(N)
834. Sum of Distances in Tree hard
https://t.me/leetcode_daily_unstoppable/60
fun sumOfDistancesInTree(n: Int, edges: Array<IntArray>): IntArray {
val graph = mutableMapOf<Int, MutableList<Int>>()
edges.forEach { (from, to) ->
graph.getOrPut(from, { mutableListOf() }) += to
graph.getOrPut(to, { mutableListOf() }) += from
}
val counts = IntArray(n) { 1 }
val sums = IntArray(n) { 0 }
fun distSum(pos: Int, visited: Int) {
graph[pos]?.forEach {
if (it != visited) {
distSum(it, pos)
counts[pos] += counts[it]
sums[pos] += counts[it] + sums[it]
}
}
}
fun dfs(pos: Int, visited: Int) {
graph[pos]?.forEach {
if (it != visited) {
sums[it] = sums[pos] - counts[it] + (n - counts[it])
dfs(it, pos)
}
}
}
distSum(0, -1)
dfs(0, -1)
return sums
}
We can do the job for item #0, then we need to invent a formula to reuse some data when we change the node.
How to mathematically prove formula for a new sum:
Store count of children in a counts
array, and sum of the distances to children in a dist
array. In a first DFS traverse from a node 0 and fill the arrays. In a second DFS only modify dist
based on previous computed dist
value, using formula: sum[curr] = sum[prev] - count[curr] + (N - count[curr])
Space: O(N), Time: O(N)
886. Possible Bipartition medium
https://t.me/leetcode_daily_unstoppable/59
fun possibleBipartition(n: Int, dislikes: Array<IntArray>): Boolean {
val love = IntArray(n+1) { it }
fun leader(x: Int): Int {
var i = x
while (love[i] != i) i = love[i]
love[x] = i
return i
}
val hate = IntArray(n+1) { -1 }
dislikes.forEach { (one, two) ->
val leaderOne = leader(one)
val leaderTwo = leader(two)
val enemyOfOne = hate[leaderOne]
val enemyOfTwo = hate[leaderTwo]
if (enemyOfOne != -1 && enemyOfOne == enemyOfTwo) return false
if (enemyOfOne != -1) {
love[leader(enemyOfOne)] = leaderTwo
}
if (enemyOfTwo != -1) {
love[leader(enemyOfTwo)] = leaderOne
}
hate[leaderOne] = leaderTwo
hate[leaderTwo] = leaderOne
}
return true
}
We need somehow to union people that hate the same people. We can do it making someone a leader of a group and make just leaders to hate each other.
Keep track of the leaders hating each other in the hate
array, and people loving their leader in love
array. (love
array is basically a Union-Find).
leader
methodSpace: O(N), Time: O(N) - adding to Union-Find is O(1) amortised
841. Keys and Rooms medium
https://t.me/leetcode_daily_unstoppable/58
fun canVisitAllRooms(rooms: List<List<Int>>): Boolean {
val visited = hashSetOf(0)
with(ArrayDeque<Int>()) {
add(0)
while(isNotEmpty()) {
rooms[poll()].forEach {
if (visited.add(it)) add(it)
}
}
}
return visited.size == rooms.size
}
We need to visit each room, and we have positions of the other rooms and a start position. This is a DFS problem. Keep all visited rooms numbers in a hash set and check the final size. Other solution is to use boolean array and a counter of the visited rooms.
Space: O(N) - for queue and visited set, Time: O(N) - visit all the rooms once
1971. Find if Path Exists in Graph easy
https://t.me/leetcode_daily_unstoppable/57
fun validPath(n: Int, edges: Array<IntArray>, source: Int, destination: Int): Boolean {
if (source == destination) return true
val graph = mutableMapOf<Int, MutableList<Int>>()
edges.forEach { (from, to) ->
graph.getOrPut(from, { mutableListOf() }).add(to)
graph.getOrPut(to, { mutableListOf() }).add(from)
}
val visited = mutableSetOf<Int>()
with(ArrayDeque<Int>()) {
add(source)
var depth = 0
while(isNotEmpty() && ++depth < n) {
repeat(size) {
graph[poll()]?.forEach {
if (it == destination) return true
if (visited.add(it)) add(it)
}
}
}
}
return false
}
BFS will do the job. Make node to nodes map, keep visited set and use queue for BFS.
Space: O(N), Time: O(N)
739. Daily Temperatures medium
https://t.me/leetcode_daily_unstoppable/55
fun dailyTemperatures(temperatures: IntArray): IntArray {
val stack = Stack<Int>()
val res = IntArray(temperatures.size) { 0 }
for (i in temperatures.lastIndex downTo 0) {
while(stack.isNotEmpty() && temperatures[stack.peek()] <= temperatures[i]) stack.pop()
if (stack.isNotEmpty()) {
res[i] = stack.peek() - i
}
stack.push(i)
}
return res
}
Intuitively, we want to go from the end of the array to the start and keep the maximum value. But, that doesn’t work, because we must also store smaller numbers, as they are closer in distance.
For example, 4 3 5 6
, when we observe 4
we must compare it to 5
, not to 6
. So, we store not just max, but increasing max: 3 5 6
, and throw away all numbers smaller than current, 3 < 4
- pop().
We will iterate in reverse order, storing indexes in increasing by temperatures stack.
Space: O(N), Time: O(N)
150. Evaluate Reverse Polish Notation medium
https://t.me/leetcode_daily_unstoppable/54
fun evalRPN(tokens: Array<String>): Int = with(Stack<Int>()) {
tokens.forEach {
when(it) {
"+" -> push(pop() + pop())
"-" -> push(-pop() + pop())
"*" -> push(pop() * pop())
"/" -> with(pop()) { push(pop()/this) }
else -> push(it.toInt())
}
}
pop()
}
Reverse polish notations made explicitly for calculation using stack. Just execute every operation immediately using last two numbers in the stack and push the result.
Space: O(N), Time: O(N)
232. Implement Queue using Stacks easy
https://t.me/leetcode_daily_unstoppable/53
class MyQueue() {
val head = Stack<Int>()
val tail = Stack<Int>()
// [] []
// 1 2 3 4 -> 4 3 2 - 1
// 5 4 3 2
// 4 3 2 5
fun push(x: Int) {
head.push(x)
}
fun pop(): Int {
peek()
return tail.pop()
}
fun peek(): Int {
if (tail.isEmpty()) while(head.isNotEmpty()) tail.push(head.pop())
return tail.peek()
}
fun empty(): Boolean = head.isEmpty() && tail.isEmpty()
}
One stack for the head of the queue and other for the tail.
When we need to do pop
we first drain from one stack to another, so items order will be restored.
Space: O(1), Time: O(1)
1143. Longest Common Subsequence medium
https://t.me/leetcode_daily_unstoppable/52
fun longestCommonSubsequence(text1: String, text2: String): Int {
val cache = Array(text1.length + 1) { IntArray(text2.length + 1) { -1 } }
fun dfs(pos1: Int, pos2: Int): Int {
if (pos1 == text1.length) return 0
if (pos2 == text2.length) return 0
val c1 = text1[pos1]
val c2 = text2[pos2]
if (cache[pos1][pos2] != -1) return cache[pos1][pos2]
val res = if (c1 == c2) {
1 + dfs(pos1 + 1, pos2 + 1)
} else {
maxOf(dfs(pos1, pos2+1), dfs(pos1+1, pos2))
}
cache[pos1][pos2] = res
return res
}
return dfs(0, 0)
}
We can walk the two strings simultaneously and compare their chars. If they are the same, the optimal way will be to use those chars and continue exploring next. If they are not, we have two choices: use the first char and skip the second or skip the first but use the second. Also, observing our algorithm we see, the result so far is only dependent of the positions from which we begin to search (and all the remaining characters). And also see that the calls are repetitive. That mean we can cache the result. (meaning this is a dynamic programming solution). Use depth first search by starting positions and memoize results in a two dimension array. Another approach will be bottom up iteration and filling the same array.
Space: O(N^2), Time: O(N^2)
198. House Robber medium
https://t.me/leetcode_daily_unstoppable/51
fun rob(nums: IntArray): Int {
val cache = mutableMapOf<Int, Int>()
fun dfs(pos: Int): Int {
if (pos > nums.lastIndex) return 0
return cache.getOrPut(pos) {
maxOf(nums[pos] + dfs(pos+2), dfs(pos+1))
}
}
return dfs(0)
}
Exploring each house one by one we can make a decision to rob or not to rob. The result is only depends on our current position (and all houses that are remaining to rob) and decision, so we can memoize it based on position.
We can use memoization or walk houses bottom up.
Space: O(N), Time: O(N)
931. Minimum Falling Path Sum medium
https://t.me/leetcode_daily_unstoppable/50
fun minFallingPathSum(matrix: Array<IntArray>): Int {
for (y in matrix.lastIndex-1 downTo 0) {
val currRow = matrix[y]
val nextRow = matrix[y+1]
for (x in 0..matrix[0].lastIndex) {
val left = if (x > 0) nextRow[x-1] else Int.MAX_VALUE
val bottom = nextRow[x]
val right = if (x < matrix[0].lastIndex) nextRow[x+1] else Int.MAX_VALUE
val minSum = currRow[x] + minOf(left, bottom, right)
currRow[x] = minSum
}
}
return matrix[0].min()!!
}
There is only three ways from any cell to it’s siblings. We can compute all three paths sums for all cells in a row so far. And then choose the smallest. Iterate over rows and compute prefix sums of current + minOf(left min sum, bottom min sum, right min sum)
Space: O(N), Time: O(N^2)
70. Climbing Stairs easy
https://t.me/leetcode_daily_unstoppable/49
val cache = mutableMapOf<Int, Int>()
fun climbStairs(n: Int): Int = when {
n < 1 -> 0
n == 1 -> 1
n == 2 -> 2
else -> cache.getOrPut(n) {
climbStairs(n-1) + climbStairs(n-2)
}
}
You can observe that result is only depend on input n. And also that result(n) = result(n-1) + result(n-2). Just use memoization for storing already solved inputs.
Space: O(N), Time: O(N)
124. Binary Tree Maximum Path Sum hard
https://t.me/leetcode_daily_unstoppable/48
fun maxPathSum(root: TreeNode?): Int {
fun dfs(root: TreeNode): Pair<Int, Int> {
val lt = root.left
val rt = root.right
if (lt == null && rt == null) return root.`val` to root.`val`
if (lt == null || rt == null) {
val sub = dfs(if (lt == null) rt else lt)
val currRes = root.`val` + sub.second
val maxRes = maxOf(sub.first, currRes, root.`val`)
val maxPath = maxOf(root.`val`, root.`val` + sub.second)
return maxRes to maxPath
} else {
val left = dfs(root.left)
val right = dfs(root.right)
val currRes1 = root.`val` + left.second + right.second
val currRes2 = root.`val`
val currRes3 = root.`val` + left.second
val currRes4 = root.`val` + right.second
val max1 = maxOf(currRes1, currRes2)
val max2 = maxOf(currRes3, currRes4)
val maxRes = maxOf(left.first, right.first, maxOf(max1, max2))
val maxPath = maxOf(root.`val`, root.`val` + maxOf(left.second, right.second))
return maxRes to maxPath
}
}
return if (root == null) 0 else dfs(root).first
}
Space: O(logN), Time: O(N)
1339. Maximum Product of Splitted Binary Tree medium
https://t.me/leetcode_daily_unstoppable/47
fun maxProduct(root: TreeNode?): Int {
fun sumDfs(root: TreeNode?): Long {
return if (root == null) 0L
else with(root) { `val`.toLong() + sumDfs(left) + sumDfs(right) }
}
val total = sumDfs(root)
fun dfs(root: TreeNode?) : Pair<Long, Long> {
if (root == null) return Pair(0,0)
val left = dfs(root.left)
val right = dfs(root.right)
val sum = left.first + root.`val`.toLong() + right.first
val productLeft = left.first * (total - left.first)
val productRight = right.first * (total - right.first)
val prevProductMax = maxOf(right.second, left.second)
return sum to maxOf(productLeft, productRight, prevProductMax)
}
return (dfs(root).second % 1_000_000_007L).toInt()
}
Just iterate over all items and compute all products. We need to compute total sum before making the main traversal.
Space: O(logN), Time: O(N)
1026. Maximum Difference Between Node and Ancestor medium
https://t.me/leetcode_daily_unstoppable/46
fun maxAncestorDiff(root: TreeNode?): Int {
root?: return 0
fun dfs(root: TreeNode, min: Int = root.`val`, max: Int = root.`val`): Int {
val v = root.`val`
val currDiff = maxOf(Math.abs(v - min), Math.abs(v - max))
val currMin = minOf(min, v)
val currMax = maxOf(max, v)
val leftDiff = root.left?.let { dfs(it, currMin, currMax) } ?: 0
val rightDiff = root.right?.let { dfs(it, currMin, currMax) } ?: 0
return maxOf(currDiff, leftDiff, rightDiff)
}
return dfs(root)
}
Based on math we can assume, that max difference is one of the two: (curr - max so far) or (curr - min so far).
Like, for example, let our curr value be 3
, and from all visited we have min 0
and max 7
.
0--3---7
Space: O(logN), Time: O(N)
https://t.me/leetcode_daily_unstoppable/45
fun leafSimilar(root1: TreeNode?, root2: TreeNode?): Boolean {
fun dfs(root: TreeNode?): List<Int> {
return when {
root == null -> listOf()
root.left == null && root.right == null -> listOf(root.`val`)
else -> dfs(root.left) + dfs(root.right)
}
}
return dfs(root1) == dfs(root2)
}
There is only 200 items, so we can concatenate lists. One optimization would be to collect only first tree and just compare it to the second tree while doing the inorder traverse.
Space: O(N), Time: O(N)
https://t.me/leetcode_daily_unstoppable/44
fun rangeSumBST(root: TreeNode?, low: Int, high: Int): Int =
if (root == null) 0 else
with(root) {
(if (`val` in low..high) `val` else 0) +
(if (`val` < low) 0 else rangeSumBST(left, low, high)) +
(if (`val` > high) 0 else rangeSumBST(right, low, high))
}
Space: O(log N), Time: O(R), r - is a range [low, high]
328. Odd Even Linked List medium
https://t.me/leetcode_daily_unstoppable/43
// 1 2
fun oddEvenList(head: ListNode?): ListNode? {
var odd = head //1
val evenHead = head?.next
var even = head?.next //2
while(even!=null) { //2
val oddNext = odd?.next?.next //null
val evenNext = even?.next?.next //null
odd?.next = oddNext // 1->null
even?.next = evenNext //2->null
if (oddNext != null) odd = oddNext //
even = evenNext // null
}
odd?.next = evenHead // 1->2
return head //1->2->null
}
Space: O(1), Time: O(n)
876. Middle of the Linked List easy
https://t.me/leetcode_daily_unstoppable/42
fun middleNode(head: ListNode?, fast: ListNode? = head): ListNode? =
if (fast?.next == null) head else middleNode(head?.next, fast?.next?.next)
Space: O(n), Time: O(n)
2256. Minimum Average Difference medium
https://t.me/leetcode_daily_unstoppable/41
fun minimumAverageDifference(nums: IntArray): Int {
var sum = 0L
nums.forEach { sum += it.toLong() }
var leftSum = 0L
var min = Long.MAX_VALUE
var minInd = 0
for (i in 0..nums.lastIndex) {
val leftCount = (i+1).toLong()
leftSum += nums[i].toLong()
val front = leftSum/leftCount
val rightCount = nums.size.toLong() - leftCount
val rightSum = sum - leftSum
val back = if (rightCount == 0L) 0L else rightSum/rightCount
val diff = Math.abs(front - back)
if (diff < min) {
min = diff
minInd = i
}
}
return minInd
}
Two pointers, one for even, one for odd indexes.
To avoid mistakes you need to be verbose, and don’t skip operations:
1->2->null
by yourselfSpace: O(1), Time: O(n)
451. Sort Characters By Frequency medium
https://t.me/leetcode_daily_unstoppable/40
fun frequencySort(s: String): String =
s.groupBy { it }
.values
.map { it to it.size }
.sortedBy { -it.second }
.map { it.first }
.flatten()
.joinToString("")
Very simple task, can be written in a functional style. Space: O(n), Time: O(n)
https://leetcode.com/problems/determine-if-two-strings-are-close/ medium
https://t.me/leetcode_daily_unstoppable/39
// cabbba -> c aa bbb -> 1 2 3
// a bb ccc -> 1 2 3
// uau
// ssx
fun closeStrings(word1: String, word2: String,
f: (String) -> List<Int> = { it.groupBy { it }.values.map { it.size }.sorted() }
): Boolean = f(word1) == f(word2) && word1.toSet() == word2.toSet()
That is a simple task, you just need to know what exactly you asked for. Space: O(n), Time: O(n)
1704. Determine if String Halves Are Alike easy
https://t.me/leetcode_daily_unstoppable/38
fun halvesAreAlike(s: String): Boolean {
val vowels = setOf('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U')
var c1 = 0
var c2 = 0
s.forEachIndexed { i, c ->
if (c in vowels) {
if (i < s.length / 2) c1++ else c2++
}
}
return c1 == c2
}
Just do what is asked.
O(N) time, O(1) space
1207. Unique Number of Occurrences easy
https://t.me/leetcode_daily_unstoppable/36
fun uniqueOccurrences(arr: IntArray): Boolean {
val counter = mutableMapOf<Int, Int>()
arr.forEach { n -> counter[n] = 1 + (counter[n] ?: 0) }
val freq = mutableSetOf<Int>()
return !counter.values.any { count -> !freq.add(count) }
}
Nothing interesting, just count and filter.
O(N) time, O(N) space
380. Insert Delete GetRandom O(1) medium
https://t.me/leetcode_daily_unstoppable/35
class RandomizedSet() {
val rnd = Random(0)
val list = mutableListOf<Int>()
val vToInd = mutableMapOf<Int, Int?>()
fun insert(v: Int): Boolean {
if (!vToInd.contains(v)) {
vToInd[v] = list.size
list.add(v)
return true
}
return false
}
fun remove(v: Int): Boolean {
val ind = vToInd[v] ?: return false
val prevLast = list[list.lastIndex]
list[ind] = prevLast
vToInd[prevLast] = ind
list.removeAt(list.lastIndex)
vToInd.remove(v)
return true
}
fun getRandom(): Int = list[rnd.nextInt(list.size)]
}
The task is simple, one trick is to remove elements from the end of the list, and replacing item with the last one. Some thoughts:
O(1) time, O(N) space
2225. Find Players With Zero or One Losses medium
https://t.me/leetcode_daily_unstoppable/34
fun findWinners(matches: Array<IntArray>): List<List<Int>> {
val winners = mutableMapOf<Int, Int>()
val losers = mutableMapOf<Int, Int>()
matches.forEach { (w, l) ->
winners[w] = 1 + (winners[w]?:0)
losers[l] = 1 + (losers[l]?:0)
}
return listOf(
winners.keys
.filter { !losers.contains(it) }
.sorted(),
losers
.filter { (k, v) -> v == 1 }
.map { (k, v) -> k}
.sorted()
)
}
Just do what is asked.
O(NlogN) time, O(N) space
446. Arithmetic Slices II - Subsequence hard
https://t.me/leetcode_daily_unstoppable/33
fun numberOfArithmeticSlices(nums: IntArray): Int {
// 0 1 2 3 4 5
// 1 2 3 1 2 3 diff = 1
// ^ ^ * dp[5][diff] =
// | | \__ curr 1 + dp[1][diff] +
// prev | 1 + dp[4][diff]
// prev
//
val dp = Array(nums.size) { mutableMapOf<Long, Long> () }
for (curr in 0..nums.lastIndex) {
for (prev in 0 until curr) {
val diff = nums[curr].toLong() - nums[prev].toLong()
dp[curr][diff] = 1 + (dp[curr][diff]?:0L) + (dp[prev][diff]?:0L)
}
}
return dp.map { it.values.sum()!! }.sum().toInt() - (nums.size)*(nums.size-1)/2
}
dp[i][d] is the number of subsequences in range [0..i] with difference = d
array: "1 2 3 1 2 3"
For items 1 2 curr = 2:
diff = 1, dp = 1
For items 1 2 3 curr = 3:
diff = 2, dp = 1
diff = 1, dp = 2
For items 1 2 3 1 curr = 1:
diff = 0, dp = 1
diff = -1, dp = 1
diff = -2, dp = 1
For items 1 2 3 1 2 curr = 2:
diff = 1, dp = 2
diff = 0, dp = 1
diff = -1, dp = 1
For items 1 2 3 1 2 3 curr = 3:
diff = 2, dp = 2
diff = 1, dp = 5
diff = 0, dp = 1
and finally, we need to subtract all the sequences of length 2 and 1, count of them is (n)*(n-1)/2
O(N^2) time, O(N^2) space
1235. Maximum Profit in Job Scheduling hard
https://t.me/leetcode_daily_unstoppable/32
fun jobScheduling(startTime: IntArray, endTime: IntArray, profit: IntArray): Int {
val n = startTime.size
val inds = Array<Int>(n) { it }
inds.sortWith (Comparator<Int> { a, b ->
if (startTime[a] == startTime[b])
endTime[a] - endTime[b]
else
startTime[a] - startTime[b]
})
val maxProfit = IntArray(n) { 0 }
maxProfit[n-1] = profit[inds[n-1]]
for (i in n-2 downTo 0) {
val ind = inds[i]
val end = endTime[ind]
val prof = profit[ind]
var lo = l + 1
var hi = n - 1
var nonOverlapProfit = 0
while (lo <= hi) {
val mid = lo + (hi - lo) / 2
if (end <= startTime[inds[mid]]) {
nonOverlapProfit = maxOf(nonOverlapProfit, maxProfit[mid])
hi = mid - 1
} else lo = mid + 1
}
maxProfit[i] = maxOf(prof + nonOverlapProfit, maxProfit[i+1])
}
return maxProfit[0]
}
Use the hints from the description. THis cannot be solved greedily, because you need to find next non-overlapping job. Dynamic programming equation: from last job to the current, result is max of next result and current + next non-overlapping result.
f(i) = max(f(i+1), profit[i] + f(j)), where j is the first non-overlapping job after i.
Also, instead of linear search for non overlapping job, use binary search.
O(NlogN) time, O(N) space
907. Sum of Subarray Minimums medium
data class V(val v: Int, val count: Int)
fun sumSubarrayMins(arr: IntArray): Int {
val M = 1_000_000_007
// 1 2 3 4 2 2 3 4
// 1 2 3 2 2 2 3
// 1 2 2 2 2 2
// 1 2 2 2 2
// 1 2 2 2
// 1 2 2
// 1 2
// 1
// f(1) = 1
// f(2) = 2>1 ? f(1) + [1, 2]
// f(3) = 3>2 ? f(2) + [1, 2, 3]
// f(4) = 4>3 ? f(3) + [1, 2, 3, 4]
// f(2) = 2<4 ? f(4) + [1, 2, 2, 2, 2] (1, 2, 3, 4 -> 3-2, 4-2, +2)
// f(2) = 2=2 ? f(2) + [1, 2, 2, 2, 2, 2]
// f(3) = 3>2 ? f(2) + [1, 2, 2, 2, 2, 2, 3]
// f(4) = 4>3 ? f(3) + [1, 2, 2, 2, 2, 2, 3, 4]
// 3 1 2 4 f(3) = 3 sum = 3 stack: [3]
// 1 1 2 f(1): 3 > 1 , remove V(3,1), sum = sum - 3 + 1*2= 2, f=3+2=5, [(1,2)]
// 1 1 f(2): 2>1, sum += 2 = 4, f+=4=9
// 1 f(4): 4>2, sum+=4=8, f+=8=17
val stack = Stack<V>()
var f = 0
var sum = 0
arr.forEach { n ->
var countRemoved = 0
while (stack.isNotEmpty() && stack.peek().v > n) {
val v = stack.pop()
countRemoved += v.count
var removedSum = (v.v*v.count) % M
if (removedSum < 0) removedSum = M + removedSum
sum = (sum - removedSum) % M
if (sum < 0) sum = sum + M
}
val count = countRemoved + 1
stack.add(V(n, count))
sum = (sum + (n * count) % M) % M
f = (f + sum) % M
}
return f
}
First attempt is to build an N^2 tree of minimums, comparing adjacent elements row by row and finding a minimum. That will take O(N^2) time and gives TLE. Next observe that there is a repetition of the results if we computing result for each new element: result = previous result + some new elements. That new elements are also have a law of repetition: sum = current element + if (current element < previous element) count of previous elements * current element else previous sum We can use a stack to keep lowest previous elements, all values in stack must be less than current element.
O(N) time, O(N) space
79. Word Search medium
fun exist(board: Array<CharArray>, word: String): Boolean {
fun dfs(y: Int, x: Int, pos: Int): Boolean {
if (pos == word.length) return true
if (y < 0 || x < 0 || y == board.size || x == board[0].size) return false
val c = board[y][x]
if (c != word[pos]) return false
board[y][x] = '.'
val res = dfs(y-1, x, pos+1)
|| dfs(y+1, x, pos+1)
|| dfs(y, x-1, pos+1)
|| dfs(y, x+1, pos+1)
board[y][x] = c
return res
}
for (y in 0..board.lastIndex) {
for (x in 0..board[0].lastIndex) {
if (dfs(y, x, 0)) return true
}
}
return false
}
We can brute force this problem. Backtracking help to preserve memory.
Complexity: O(MNW) Memory: O(W)
https://leetcode.com/problems/valid-sudoku/ medium
fun isValidSudoku(board: Array<CharArray>): Boolean {
val cell9 = arrayOf(0 to 0, 0 to 1, 0 to 2,
1 to 0, 1 to 1, 1 to 2,
2 to 0, 2 to 1, 2 to 2)
val starts = arrayOf(0 to 0, 0 to 3, 0 to 6,
3 to 0, 3 to 3, 3 to 6,
6 to 0, 6 to 3, 6 to 6)
return !starts.any { (sy, sx) ->
val visited = HashSet<Char>()
cell9.any { (dy, dx) ->
val c = board[sy+dy][sx+dx]
c != '.' && !visited.add(c)
}
} && !board.any { row ->
val visited = HashSet<Char>()
row.any { it != '.' && !visited.add(it) }
} && !(0..8).any { x ->
val visited = HashSet<Char>()
(0..8).any { board[it][x] != '.' && !visited.add(board[it][x]) }
}
}
This is an easy problem, just do what is asked.
Complexity: O(N) Memory: O(N), N = 81, so it O(1)
https://leetcode.com/problems/perfect-squares/ medium
val cache = mutableMapOf<Int, Int>()
fun numSquares(n: Int): Int {
if (n < 0) return -1
if (n == 0) return 0
if (cache[n] != null) return cache[n]!!
var min = Int.MAX_VALUE
for (x in Math.sqrt(n.toDouble()).toInt() downTo 1) {
val res = numSquares(n - x*x)
if (res != -1) {
min = minOf(min, 1 + res)
}
}
if (min == Int.MAX_VALUE) min = -1
cache[n] = min
return min
}
The problem gives stable answers for any argument n. So, we can use memoization technique and search from the biggest square to the smallest one.
Complexity: O(Nsqrt(N)) Memory: O(N)
https://leetcode.com/problems/nearest-exit-from-entrance-in-maze/ medium
fun nearestExit(maze: Array<CharArray>, entrance: IntArray): Int {
val queue = ArrayDeque<Pair<Int, Int>>()
queue.add(entrance[1] to entrance[0])
maze[entrance[0]][entrance[1]] = 'x'
var steps = 1
val directions = intArrayOf(-1, 0, 1, 0, -1)
while(queue.isNotEmpty()) {
repeat(queue.size){
val (x, y) = queue.poll()
for (i in 1..directions.lastIndex) {
val nx = x + directions[i-1]
val ny = y + directions[i]
if (nx in 0..maze[0].lastIndex &&
ny in 0..maze.lastIndex &&
maze[ny][nx] == '.') {
if (nx == 0 ||
ny == 0 ||
nx == maze[0].lastIndex ||
ny == maze.lastIndex) return steps
maze[ny][nx] = 'x'
queue.add(nx to ny)
}
}
}
steps++
}
return -1
}
Just do BFS.
Complexity: O(N), N - number of cells in maze Memory: O(N)
https://leetcode.com/problems/basic-calculator/ hard
fun calculate(s: String): Int {
var i = 0
var sign = 1
var eval = 0
while (i <= s.lastIndex) {
val chr = s[i]
if (chr == '(') {
//find the end
var countOpen = 0
for (j in i..s.lastIndex) {
if (s[j] == '(') countOpen++
if (s[j] == ')') countOpen--
if (countOpen == 0) {
//evaluate substring
eval += sign * calculate(s.substring(i+1, j)) // [a b)
sign = 1
i = j
break
}
}
} else if (chr == '+') {
sign = 1
} else if (chr == '-') {
sign = -1
} else if (chr == ' ') {
//nothing
} else {
var num = (s[i] - '0').toInt()
for (j in (i+1)..s.lastIndex) {
if (s[j].isDigit()) {
num = num * 10 + (s[j] - '0').toInt()
i = j
} else break
}
eval += sign * num
sign = 1
}
i++
}
return eval
}
This is a classic calculator problem, nothing special.
Complexity: O(N) Memory: O(N), because of the recursion, worst case is all the input is brackets
https://leetcode.com/problems/erect-the-fence/ hard
fun outerTrees(trees: Array<IntArray>): Array<IntArray> {
if (trees.size <= 3) return trees
trees.sortWith(Comparator { a, b -> if (a[0]==b[0]) a[1]-b[1] else a[0] - b[0]} )
fun cmp(a: IntArray, b: IntArray, c: IntArray): Int {
val xab = b[0] - a[0]
val yab = b[1] - a[1]
val xbc = c[0] - b[0]
val ybc = c[1] - b[1]
return xab*ybc - yab*xbc
}
val up = mutableListOf<IntArray>()
val lo = mutableListOf<IntArray>()
trees.forEach { curr ->
while(up.size >= 2 && cmp(up[up.size-2], up[up.size-1], curr) < 0) up.removeAt(up.lastIndex)
while(lo.size >= 2 && cmp(lo[lo.size-2], lo[lo.size-1], curr) > 0) lo.removeAt(lo.lastIndex)
up.add(curr)
lo.add(curr)
}
return (up+lo).distinct().toTypedArray()
}
This is an implementation of the Andrew’s monotonic chain algorithm.
Complexity: O(Nlog(N)) Memory: O(N)
https://leetcode.com/problems/ugly-number/ easy
fun isUgly(n: Int): Boolean {
if (n <= 0) return false
var x = n
while(x%2==0) x = x/2
while(x%3==0) x = x/3
while(x%5==0) x = x/5
return x == 1
}
There is also a clever math solution, but I don’t understand it yet.
Complexity: O(log(n)) Memory: O(1)
https://leetcode.com/problems/rectangle-area/ middle
class Solution {
class P(val x: Int, val y: Int)
class Rect(val l: Int, val t: Int, val r: Int, val b: Int) {
val corners = arrayOf(P(l, t), P(l, b), P(r, t), P(r, b))
val s = (r - l) * (t - b)
fun contains(p: P) = p.x in l..r && p.y in b..t
fun intersect(o: Rect): Rect {
val allX = intArrayOf(l, r, o.l, o.r).apply { sort() }
val allY = intArrayOf(b, t, o.b, o.t).apply { sort() }
val r = Rect(allX[1], allY[2], allX[2], allY[1])
return if (r.corners.all { contains(it) && o.contains(it)})
r else Rect(0,0,0,0)
}
}
fun computeArea(ax1: Int, ay1: Int, ax2: Int, ay2: Int, bx1: Int, by1: Int, bx2: Int, by2: Int): Int {
val r1 = Rect(ax1, ay2, ax2, ay1)
val r2 = Rect(bx1, by2, bx2, by1)
return r1.s + r2.s - r1.intersect(r2).s
}
}
This is an OOP problem. One trick to write intersection function is to notice that all corners of intersection rectangle must be inside both rectangles. Also, intersection rectangle formed from middle coordinates of all corners sorted by x and y.
Complexity: O(1) Memory: O(1)
https://leetcode.com/problems/guess-number-higher-or-lower/ easy
override fun guessNumber(n:Int):Int {
var lo = 1
var hi = n
while(lo <= hi) {
val pick = lo + (hi - lo)/2
val answer = guess(pick)
if (answer == 0) return pick
if (answer == -1) hi = pick - 1
else lo = pick + 1
}
return lo
}
This is a classic binary search algorithm. The best way of writing it is:
Complexity: O(log(N)) Memory: O(1)
https://leetcode.com/problems/count-complete-tree-nodes/ medium
x
* x
* * x
* x * x
* x x x x * x x
\
on each node we can check it's left and right depths
this only takes us O(logN) time on each step
there are logN steps in total (height of the tree)
so the total time complexity is O(log^2(N))
fun countNodes(root: TreeNode?): Int {
var hl = 0
var node = root
while (node != null) {
node = node.left
hl++
}
var hr = 0
node = root
while (node != null) {
node = node.right
hr++
}
return when {
hl == 0 -> 0
hl == hr -> (1 shl hl) - 1
else -> 1 +
(root!!.left?.let {countNodes(it)}?:0) +
(root!!.right?.let {countNodes(it)}?:0)
}
}
Complexity: O(log^2(N)) Memory: O(logN)
https://leetcode.com/problems/most-stones-removed-with-same-row-or-column/ medium
From observing the problem, we can see, that the task is in fact is to find an isolated islands:
// * 3 * * 3 * * * *
// 1 2 * -> * * * or 1 * *
// * * 4 * * 4 * * 4
// * 3 * * * *
// 1 2 5 -> * * *
// * * 4 * * 4
fun removeStones(stones: Array<IntArray>): Int {
val uf = IntArray(stones.size) { it }
var rootsCount = uf.size
fun root(a: Int): Int {
var x = a
while (uf[x] != x) x = uf[x]
return x
}
fun union(a: Int, b: Int) {
val rootA = root(a)
val rootB = root(b)
if (rootA != rootB) {
uf[rootA] = rootB
rootsCount--
}
}
val byY = mutableMapOf<Int, MutableList<Int>>()
val byX = mutableMapOf<Int, MutableList<Int>>()
stones.forEachIndexed { i, st ->
byY.getOrPut(st[0], { mutableListOf() }).add(i)
byX.getOrPut(st[1], { mutableListOf() }).add(i)
}
byY.values.forEach { list ->
if (list.size > 1)
for (i in 1..list.lastIndex) union(list[0], list[i])
}
byX.values.forEach { list ->
if (list.size > 1)
for (i in 1..list.lastIndex) union(list[0], list[i])
}
return stones.size - rootsCount
}
Complexity: O(N) Memory: O(N)
https://leetcode.com/problems/reverse-words-in-a-string/ medium
A simple trick: reverse all the string, then reverse each word.
fun reverseWords(s: String): String {
val res = StringBuilder()
val curr = Stack<Char>()
(s.lastIndex downTo 0).forEach { i ->
val c = s[i]
if (c in '0'..'z') curr.push(c)
else if (curr.isNotEmpty()) {
if (res.length > 0) res.append(' ')
while (curr.isNotEmpty()) res.append(curr.pop())
}
}
if (curr.isNotEmpty() && res.length > 0) res.append(' ')
while (curr.isNotEmpty()) res.append(curr.pop())
return res.toString()
}
Complexity: O(N) Memory: O(N) - there is no O(1) solution for string in JVM
https://leetcode.com/problems/find-median-from-data-stream/ hard
To find the median we can maintain two heaps: smaller and larger. One decreasing and one increasing. Peeking the top from those heaps will give us the median.
// [5 2 0] [6 7 10]
// dec inc
// ^ peek ^ peek
class MedianFinder() {
val queDec = PriorityQueue<Int>(reverseOrder())
val queInc = PriorityQueue<Int>()
fun addNum(num: Int) {
if (queDec.size == queInc.size) {
queInc.add(num)
queDec.add(queInc.poll())
} else {
queDec.add(num)
queInc.add(queDec.poll())
}
}
fun findMedian(): Double = if (queInc.size == queDec.size)
(queInc.peek() + queDec.peek()) / 2.0
else
queDec.peek().toDouble()
}
Complexity: O(NlogN) Memory: O(N)
https://leetcode.com/problems/remove-duplicates-from-sorted-array/ easy
Just do what is asked. Keep track of the pointer to the end of the “good” part.
fun removeDuplicates(nums: IntArray): Int {
var k = 0
for (i in 1..nums.lastIndex) {
if (nums[k] != nums[i]) nums[++k] = nums[i]
}
return k + 1
}
Complexity: O(N) Memory: O(1)
https://leetcode.com/problems/remove-all-adjacent-duplicates-in-string/ easy
Solution:
fun removeDuplicates(s: String): String {
val stack = Stack<Char>()
s.forEach { c ->
if (stack.isNotEmpty() && stack.peek() == c) {
stack.pop()
} else {
stack.push(c)
}
}
return stack.joinToString("")
}
Explanation: Just scan symbols one by one and remove duplicates from the end. Complexity: O(N) Memory: O(N)
https://leetcode.com/problems/online-stock-span/ medium
So, we need to keep increasing sequence of numbers, increasing/decreasing stack will help. Consider example, this is how decreasing stack will work
// 100 [100-1] 1
// 80 [100-1, 80-1] 1
// 60 [100-1, 80-1, 60-1] 1
// 70 [100-1, 80-1, 70-2] + 60 2
// 60 [100-1, 80-1, 70-2, 60-1] 1
// 75 [100-1, 80-1, 75-4] + 70-2+60-1 4
// 85 [100-1, 85-6] 80-1+75-4 6
Solution:
class StockSpanner() {
val stack = Stack<Pair<Int,Int>>()
fun next(price: Int): Int {
// 100 [100-1] 1
// 80 [100-1, 80-1] 1
// 60 [100-1, 80-1, 60-1] 1
// 70 [100-1, 80-1, 70-2] + 60 2
// 60 [100-1, 80-1, 70-2, 60-1] 1
// 75 [100-1, 80-1, 75-4] + 70-2+60-1 4
// 85 [100-1, 85-6] 80-1+75-4 6
var span = 1
while(stack.isNotEmpty() && stack.peek().first <= price) {
span += stack.pop().second
}
stack.push(price to span)
return span
}
}
Complexity: O(N) Memory: O(N)
https://leetcode.com/problems/make-the-string-great/ easy
fun makeGood(s: String): String {
var ss = s.toCharArray()
var finished = false
while(!finished) {
finished = true
for (i in 0 until s.lastIndex) {
if (ss[i] == '.') continue
var j = i+1
while(j <= s.lastIndex && ss[j] == '.') {
j++
continue
}
if (j == s.length) break
var a = ss[i]
var b = ss[j]
if (a != b && Character.toLowerCase(a) ==
Character.toLowerCase(b)) {
ss[i] = '.'
ss[j] = '.'
finished = false
}
}
}
return ss.filter { it != '.' }.joinToString("")
}
Explanation: The simplest solution is just to simulate all the process, as input string is just 100 symbols.
Speed: O(n^2) Memory: O(n)
https://leetcode.com/problems/maximum-69-number/ easy
fun maximum69Number (num: Int): Int {
var n = num
if (6666 <= n && n <= 6999) return num + 3000
if (n > 9000) n -= 9000
if (666 <= n && n <= 699) return num + 300
if (n > 900) n -= 900
if (66 <= n && n <= 69) return num + 30
if (n > 90) n -= 90
if (6 == n) return num + 3
return num
}
Explanation: The simplest implementations would be converting to array of digits, replacing the first and converting back. However we can observe that numbers are in range 6-9999, so we can hardcode some logic.
Speed: O(1), Memory: O(1)
https://leetcode.com/problems/orderly-queue/ hard
fun orderlyQueue(s: String, k: Int): String {
val chrs = s.toCharArray()
if (k == 1) {
var smallest = s
for (i in 0..s.lastIndex) {
val prefix = s.substring(0, i)
val suffix = s.substring(i)
val ss = suffix + prefix
if (ss.compareTo(smallest) < 0) smallest = ss
}
return smallest
} else {
chrs.sort()
return String(chrs)
}
}
O(n^2)
Explanation: One idea that come to my mind is: if k >= 2 then you basically can swap any adjacent elements. That means you can actually sort all the characters.
Speed: O(n^2), Memory: O(n)
https://leetcode.com/problems/word-search-ii/ hard
Solution [kotlin]
class Node {
val next = Array<Node?>(26) { null }
var word: String? = null
operator fun invoke(c: Char): Node {
val ind = c.toInt() - 'a'.toInt()
if (next[ind] == null) next[ind] = Node()
return next[ind]!!
}
operator fun get(c: Char) = next[c.toInt() - 'a'.toInt()]
}
fun findWords(board: Array<CharArray>, words: Array<String>): List<String> {
val trie = Node()
words.forEach { w ->
var t = trie
w.forEach { t = t(it) }
t.word = w
}
val result = mutableSetOf<String>()
fun dfs(y: Int, x: Int, t: Node?, visited: MutableSet<Int>) {
if (t == null || y < 0 || x < 0
|| y >= board.size || x >= board[0].size
|| !visited.add(100 * y + x)) return
t[board[y][x]]?.let {
it.word?.let { result.add(it) }
dfs(y-1, x, it, visited)
dfs(y+1, x, it, visited)
dfs(y, x-1, it, visited)
dfs(y, x+1, it, visited)
}
visited.remove(100 * y + x)
}
board.forEachIndexed { y, row ->
row.forEachIndexed { x, c ->
dfs(y, x, trie, mutableSetOf<Int>())
}
}
return result.toList()
}
Explanation: Use trie + dfs
Speed: O(wN + M), w=10, N=10^4, M=12^2 , Memory O(26w + N)
https://leetcode.com/problems/reverse-vowels-of-a-string/ easy
Solution [kotlin]
fun reverseVowels(s: String): String {
val vowels = setOf('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U')
var chrs = s.toCharArray()
var l = 0
var r = chrs.lastIndex
while(l < r) {
while(l<r && chrs[l] !in vowels) l++
while(l<r && chrs[r] !in vowels) r--
if (l < r) chrs[l] = chrs[r].also { chrs[r] = chrs[l] }
r--
l++
}
return String(chrs)
}
Explanation: Straightforward solution : use two pointers method and scan from the both sides.
Speed: O(N), Memory O(N)
https://leetcode.com/problems/longest-palindrome-by-concatenating-two-letter-words/ medium
Solution [kotlin]
fun longestPalindrome(words: Array<String>): Int {
var singles = 0
var mirrored = 0
var uneven = 0
var unevenSum = 0
val visited = mutableMapOf<String, Int>()
words.forEach { w -> visited[w] = 1 + visited.getOrDefault(w, 0) }
visited.forEach { w, wCount ->
if (w[0] == w[1]) {
if (wCount %2 == 0) {
singles += wCount*2
} else {
// a b -> a
// a b a -> aba 2a + 1b = 2 + 1
// a b a b -> abba 2a + 2b = 2+2
// a b a b a -> baaab 3a + 2b = 3+2
// a b a b a b -> baaab 3a + 3b = 3+2 (-1)
// a b a b a b a -> aabbbaa 4a+3b=4+3
// a b a b a b a b -> aabbbbaa 4a+4b=4+4
// 5a+4b = 2+5+2
// 5a+5b = 2+5+2 (-1)
// 1c + 2b + 2a = b a c a b
// 1c + 3b + 2a =
// 1c + 3b + 4a = 2a + 3b + 2a
// 5d + 3a + 3b + 3c = a b c 5d c b a = 11
uneven++
unevenSum += wCount
}
} else {
val matchingCount = visited[w.reversed()] ?:0
mirrored += minOf(wCount, matchingCount)*2
}
}
val unevenCount = if (uneven == 0) 0 else 2*(unevenSum - uneven + 1)
return singles + mirrored + unevenCount
}
Explanation: This is a counting task, can be solved linearly. There are 3 cases:
Speed: O(N), Memory O(N)
https://leetcode.com/problems/minimum-genetic-mutation/ medium
Solution [kotlin]
fun minMutation(start: String, end: String, bank: Array<String>): Int {
val wToW = mutableMapOf<Int, MutableList<Int>>()
fun searchInBank(i1: Int, w1: String) {
bank.forEachIndexed { i2, w2 ->
if (w1 != w2) {
var diffCount = 0
for (i in 0..7) {
if (w1[i] != w2[i]) diffCount++
}
if (diffCount == 1) {
wToW.getOrPut(i1, { mutableListOf() }).add(i2)
wToW.getOrPut(i2, { mutableListOf() }).add(i1)
}
}
}
}
bank.forEachIndexed { i1, w1 -> searchInBank(i1, w1) }
searchInBank(-1, start)
val queue = ArrayDeque<Int>()
queue.add(-1)
var steps = 0
while(queue.isNotEmpty()) {
repeat(queue.size) {
val ind = queue.poll()
val word = if (ind == -1) start else bank[ind]
if (word == end) return steps
wToW[ind]?.let { siblings ->
siblings.forEach { queue.add(it) }
}
}
steps++
if (steps > bank.size + 1) return -1
}
return -1
}
Explanation:
Speed: O(wN^2), Memory O(N)
https://leetcode.com/problems/where-will-the-ball-fall/ medium
Solution [kotlin]
fun findBall(grid: Array<IntArray>): IntArray {
var indToBall = IntArray(grid[0].size) { it }
var ballToInd = IntArray(grid[0].size) { it }
grid.forEach { row ->
var nextIndToBall = IntArray(grid[0].size) { -1 }
var nextBallToInd = IntArray(grid[0].size) { -1 }
for (i in 0..row.lastIndex) {
val currBall = indToBall[i]
if (currBall != -1) {
val isCorner = row[i] == 1
&& i<row.lastIndex
&& row[i+1] == -1
|| row[i] == -1
&& i > 0
&& row[i-1] == 1
val newInd = i + row[i]
if (!isCorner && newInd >= 0 && newInd <= row.lastIndex) {
nextIndToBall[newInd] = currBall
nextBallToInd[currBall] = newInd
}
}
}
indToBall = nextIndToBall
ballToInd = nextBallToInd
}
return ballToInd
}
Explanation: This is a geometry problem, but seeing the pattern might help. We can spot that each row is an action sequence: -1 -1 -1 shifts balls left, and 1 1 1 shifts balls to the right. Corners can be formed only with -1 1 sequence.
https://leetcode.com/problems/toeplitz-matrix/ easy
Solution [kotlin]
fun isToeplitzMatrix(matrix: Array<IntArray>): Boolean =
matrix
.asSequence()
.windowed(2)
.all { (prev, curr) -> prev.dropLast(1) == curr.drop(1) }
Explanation: just compare adjacent rows, they must have an equal elements except first and last
]]>Sometimes in everyday Android development, you encounter strange, if not mystical bugs. (standard introduction)
In the previous episode Databinding Episode I; Hidden Danger
Let’s assume there is a *.kt class:
Base.kt
open class Base {
open fun isEmpty() = false
}
and its *.java inheritor:
Child.java
class Child extends Base {
public boolean isEmpty = true;
}
Here we can already notice that we have shot ourselves in the foot. But let’s continue.
We wanted to use the Child class in databinding.
Attention, question: will the following View be visible?
some.xml
<layout>
<data>
<variable name="child" type="Child" />
</data>
<View
...
android:visibility="@{child.isEmpty ? View.VISIBLE : View.GONE}"
...
First step - we buy this chunky guy Update: on their site, only the full version with internals THICC-15 is left
What is it: an empty laptop without a processor, RAM, hard drive.
Why choose it?
Firstly, without these components, the duty will be less. Secondly, it’s the only one of its kind (apart from its twins, like Xmg Apex-15) laptop supporting a desktop Ryzen 9.
While the package is on its way, we quickly buy ourselves a Ryzen 3950x (because an Android project is serious business)
Ordered, received, unpacked, happy?
Unfortunately, it’s slightly too noisy even without heavy load. 16 cores after all.
Thinking about what to do with it.
Literally. We take a jigsaw, remove the cover and carefully cut out the middle of it. O_O
No turning back now :)
Now we need a mesh. We cut it out of an old laptop stand.
Glue the mesh to the frame of the cover with epoxy resin and super glue.
Almost ready.
We buy small copper plates for cooling separately. Height ~3.5 mm.
We will attach them on top of the cooling system, so we need to remove the labels-handles from it and clean the surface.
Copper wire and heat-resistant rubber rings will be suitable for attachment.
Don’t forget to apply thermal paste, as the surface is uneven.
All done, ready to assemble.
The laptop is suitable as a portable desktop computer. It’s convenient when your entire environment is always with you in full power.
]]>Recording of this talk - Experiment with Kotlin Mobile Mutliplatform
+9 MB for ktor,
+2MB for Kotlin serialization
Comparison of the final app size for iOS vs Android:
A good talk-interview on the topic: Chasing two rabbits: is it necessary to be able to develop for Android and iOS Alexey Gladkov. Technical architect at Leroy Merlin
]]>Current actively developed lints are Detekt and ktlint. klint have a don't overengineer
philosophy while detekt is more configurable.
https://github.com/detekt/detekt
Download binary from here https://github.com/detekt/detekt/releases Config file can be auto generated from command line or simple download mine default-detekt-config.yml
https://github.com/pinterest/ktlint
Download binary from here https://github.com/pinterest/ktlint/releases
Ktlint can be configured with standard .editorconfig
file. You can find one anywhere in github or get mine:
.editorconfig
Copy and configure script from here: pre-commit
#!/bin/bash
# Setting up guide http://dmitrysamoylenko.com/2020/12/06/configure_lints.html
# https://github.com/checkstyle/checkstyle
# https://github.com/pinterest/ktlint
# https://github.com/detekt/detekt
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ]; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
GIT_ROOT_DIR=$(git rev-parse --show-toplevel)
cd ${GIT_ROOT_DIR}
#echo ${GIT_ROOT_DIR}
f=()
while read line; do
if [ -n "$line" ]; then
if [[ "$line" =~ .*"/build/".* ]]; then
true #skip generated code
else
f+=("$line")
fi
fi
done <<<"$(git diff --diff-filter=d --staged --name-only)"
filesJava=""
filesKt=""
countJava=0
countKt=0
for i in "${!f[@]}"; do
if [[ "${f[i]}" == *.java ]]; then
filesJava+=" ${f[i]}"
countJava=$((countJava + 1))
fi
if [[ "${f[i]}" == *.kt ]]; then
filesKt+=" ${f[i]}"
countKt=$((countKt + 1))
fi
done
for i in "${!f[@]}"; do
if [[ "${f[i]}" =~ .*.(java|kt)$ ]]; then
lineNum=0
while IFS= read line; do
if [ -n "$line" ]; then
lineNum=$((lineNum+1))
if [[ "$line" =~ ^\ +[^\*].* ]]; then
echo "Line starts with spaces. Please apply project code style. File: ${f[i]}:$lineNum, line: $line"
exit 1
elif [[ "$line" =~ .*oleg.*|.*xoxoxo.* ]]; then
echo "forbidden word in line. File: ${f[i]}:$lineNum, line: $line"
exit 1
else
true #skip good code
fi
fi
done < "${f[i]}"
fi
done
if [ ${#filesJava} -eq 0 ]; then
echo "No *.java files to check."
else
configloc=-Dconfig_loc=${GIT_ROOT_DIR}/ivi/config/checkstyle
config=${GIT_ROOT_DIR}/ivi/config/checkstyle/checkstyle.xml
params="${configloc} -jar ${GIT_ROOT_DIR}/githooks/checkstyle-8.38-all.jar -c ${config}${filesJava}"
${JAVACMD} $params
result=$?
if [ $result -ne 0 ]; then
echo "Please fix the checkstyle problems before submit the commit!"
exit $result
else
echo "#java files: $countJava"
fi
fi
if [ ${#filesKt} -eq 0 ]; then
echo "No *.kt files to check."
exit 0
fi
# ktlint check
git diff --diff-filter=d --staged --name-only | grep '\.kt[s"]\?$' | xargs ./ktlint .
result=$?
if [ $result -ne 0 ]; then
echo "Please fix the ktlint problems before submit the commit!"
exit $result
fi
#detekt check
check_by_detekt() {
arg=$1
files=${arg%?}
if [ ${#files} -eq 0 ]; then
true #skip
else
params="--fail-fast --config default-detekt-config.yml --input ${files}"
./detekt ${params}
result=$?
if [ $result -ne 0 ]; then
echo "Please fix the detekt problems before submit the commit!"
exit $result
fi
fi
}
count=0
filesd=""
for i in "${!f[@]}"; do
if [[ "${f[i]}" == *.kt ]]; then
filesd+="${f[i]},"
count=$((count + 1))
# split into batches
if [ $count -gt 1000 ]; then
check_by_detekt $filesd
count=0
filesd=""
fi
fi
done
check_by_detekt $filesd
echo "# kt files: $count"
exit 0
Make folder githooks/
in the root git project directory and put all downloaded files into it.
Then in top of the project build.gradle
insert:
exec {
executable './../enable_lints.sh'
}
This will execute the script enable_lints.sh
that will apply git path to the githooks/
directory and make git execute the script pre-commit
before each commit. Contents of the script:
enable_lints.sh
#!/bin/bash
git config --global core.hooksPath githooks
Remember to make each script executable
chmod +x ./ktlint
chmod +x ./detekt
chmod +x ./githooks/pre-commit
Now add the githook/
directory and all new files to git and push it to server. All team should just open project again so AndroidStudio will run build.gradle
script.
Notice that script support only unix os and should be specially edited to support windows.
Those two collections are all sorted and very useful when you need to maintain some order in dynamically filled data. However, when concurrency takes place there is a gotcha. We have a choice: use manual synchronization or use ConcurrentSkipListSet from java.concurrent package.
I have a task that gets episodes from a server in a bulk operation. For example, when there is an 800 episodes total it makes 8 asynchronous requests 100 episodes each. Resulting list of episodes needs to be sorted and displayed on the client side.
Code for manual synchronizations looks like this:
final Set<Video> allEpisodes = new TreeSet<>(sortByEpisode);
final Video[] sortedEpisodes;
synchronized (allEpisodes) {
Collections.addAll(allEpisodes, notFakeVideos);
sortedEpisodes = allEpisodes.toArray(Video.EMPTY_ARRAY);
}
//use of sortedEpisodes
which is somewhat verbose.
In the opposite, code with concurrent collection looks very clean.:
final Set<Video> allEpisodes = new ConcurrentSkipListSet<>(sortByEpisode);
Collections.addAll(allEpisodes, notFakeVideos);
final Video[] sortedEpisodes = allEpisodes.toArray(Video.EMPTY_ARRAY);
Almost none of the concurrent stuff is visible to the programmer which is a good thing for maintainability and error-proneness.
I’m tested two solutions using Android emulator API 30. Here are results:
solution | avg ns | min ns | max ns | # of experiments | sum ns |
---|---|---|---|---|---|
concurrent | 114960 | 6543 | 4109831 | 620 | 71275591 |
synchronized | 44295 | 7035 | 1525028 | 620 | 27463261 |
For my use case manual synchronization is almost twice as fast as using concurrent collection.
Winner: TreeSet+synchronized!
However, in broad case the answer is: “It depends”.
My case consist of only 10 invocations of adding elements and one toArray()
call. It is a tiny number of concurrent events.
That’s why concurrent collection overhead makes a visible difference for performance.
I should say upfront that this article is not intended to nitpick this library, as I personally find it quite appealing. First, we’ll create a simple list to add views to. This is an inefficient method, but it will illustrate the overall picture of how much attention was paid to performance in the new framework. So, let’s compare the old method:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val N = 100_000
setContentView(
ScrollView(this).apply {
addView(
LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
repeat(N) {
addView(
TextView(context)
.apply { text = "hello $it" })
}
})
})
}
}
And the new method:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val N = 100_000
setContent {
MyApplicationTheme {
Surface(color = MaterialTheme.colors.background) {
ScrollableColumn {
repeat(N) {
Text(text = "Hello $it")
}
}
}
}
}
}
}
This is a simple scrolling list, where N text views are arranged vertically.
We measure performance using Android Studio’s Profiler
tab after the heap growth stabilizes.
N = 1 Heap size for View - 1.5M vs Compose - 2.7M This is the basic difference with just one view. Twice as much, but not critical for modern devices.
N = 40,000 View 723M vs Compose 26778M You can see how memory usage significantly increases depending on the number of elements in compose.
N = 60,000 here the emulator ran out of heap space of 512 MB (with allocated RAM=30GB) and compose crashed with OutOfMemoryError
N = 100,000 continue testing View - 1807M. There’s a huge potential for growth in the number of elements present at the same time.
Let’s plot the memory growth against the number of elements. Memory usage for View grows linearly, which is not the case for Compose.
It takes just 3 lines to create a lazy list like RecyclerView
LazyColumnFor(items = (1..1_000_000).toList()) {
Text(text="Hello $it")
}
For comparison, achieving the same result using RecyclerView:
RecyclerView(this).apply {
adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
object : RecyclerView.ViewHolder(TextView(context)) {}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder.itemView as TextView).text = "hello $position"
}
override fun getItemCount() = N
}
layoutManager = LinearLayoutManager(context)
}
Let’s take a closer look at the Lazy list. It’s interesting to see how often it redraws elements. Let’s draw multicolored circles using Canvas:
LazyColumnFor(items = (1..N).toList()) {
Text(text = "Hello $it")
Canvas(modifier = Modifier.size(10.dp), onDraw = {
this.drawOval(
color = Color(
Math.random().toFloat(),
Math.random().toFloat(),
Math.random().toFloat()
), size = this.size.times(3.0f)
)
})
}
As we can see, Canvas() redraws every tick
However, Text() redraws only when it leaves the visible area:
This is encouraging: they are thinking about component redraw optimizations in advance.
Also, the LazyColumnFor(items)
function interface does not yet allow creating a truly infinite list. A finite set of items is always expected.
Jetpack Compose is still in alpha version and it’s evident that priority is given to API conciseness. Let’s hope that Google doesn’t stop at the first iteration and optimizes the components.
After all, it’s their own slogan - #pert_matters, which they gradually seem to forget https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE)
]]>It can be found empirically. Set a breakpoint in the Application.onCreate callback and you will discover it at the top of the stack trace, in the class ActivityThread. Here it is, the “familiar” entry point of a JVM application:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Skipping things irrelevant to our topic, here’s what’s happening:
Looper.prepareMainLooper()
“prepares” the Looper (creates a thread-local instance of the Looper class)ActivityThread thread =...
An instance of ActivityThread is createdthread.attach
The attach method of ActivityThread is executed (creates an instance of the application and calls the Application.onCreate callback)Looper.loop()
The looper starts. From this moment, the looper begins to poll its internal queue and execute
tasks from it. All other activity callbacks will end up here.throw new RuntimeException("Main thread loop unexpectedly exited")
The last line reveals
the essence of the SDK architecture - the application should not reach this line in its lifetime. If we do,
the application terminates with an error.
Thus, a problem and its solution emerge: if some random Activity or View callback throws an error, the entire application crashes. There’s no way to safeguard against this in advance.
In Java, there is a universal global mechanism for catching errors: Thread.currentThread().setUncaughtExceptionHandler((t, e) -> ... ); //ловим все ошибки
But the above architecture doesn’t allow this mechanism to be used, as the error will first be thrown in the Looper.loop() call, ending it, and only then it will come out in main() and our set error handler. Once the error is caught by the handler, we are left with the problem of the completed Looper.loop, and therefore an application that no longer responds to system callbacks and clicks.
To get the activity to respond to clicks and callbacks again, just restart the looper:
Looper.loop();
So, the final solution. In Application.onCreate:
Thread.currentThread().setUncaughtExceptionHandler((t, e) -> continueSafeLoop(e));
public static void continueSafeLoop(final Throwable e) {
Throwable error = e;
while (true) {
Assert.fail(error);
try {
final Looper looper = Looper.myLooper();
new Handler(looper).removeCallbacksAndMessages(null);
final MessageQueue queue = ReflectUtils.readField(looper, "mQueue");
final Long ptr = ReflectUtils.readField(queue, "mPtr");
final Boolean quitting = ReflectUtils.readField(queue, "mQuitting");
if ((ptr == null || ptr != 0) && (quitting == null || !quitting.booleanValue())) {
Looper.loop();
} else {
break; //this is just detecting the end of the looper; done only reflectively
}
} catch (final Throwable err) {
error = err;
}
}
}
Now you can experiment by throwing an error in any of the callbacks and see that the application does not crash and does not freeze. Of course, you will still need to abstract away from direct activity callbacks, as there is a detect call to the super method. And you must ensure sending caught crashes to Firebase/Crashlytics, as there will no longer be crashes in the standard error reports in the Google Play console.
P.S.: It is noteworthy that the class ActivityThread is not a Thread and is not about the Android class Activity, but rather about “activity” in the sense of “a set of actions,” and creates not one activity, but the entire Application. In essence, it acts as a delegate of all system callbacks of the application, responsible for its state transitions. This is also stated in its JavaDoc:
/**
* This manages the execution of the main thread in an
* application process, scheduling and executing activities,
* broadcasts, and other operations on it as the activity
* manager requests.
*
* {@hide}
*/
A bit of an unfortunate name choice, in my opinion :)
]]>