In this repo you can find:
- Leetcode
- Solutions and (rarely) explanations for yandex 2025 autumn contest
- My implementations of basic data sctructures (at 2025-01-03 only list is implemented)
-
Array
- Two Sum
- Best Time to Buy and Sell Stock
- Contains Duplicate
- Product of Array Except Self
- Maximum Subarray
- Maximum Product Subarray
- time - O(N), space - (1):
- Main idea: iterate through array and update Max and Min possible product, which includes current value. Edge case: if val == 0, then Max=1,Min=1
- Step by step:
- initialize
Max,Minandresint Max = 1, Min = 1, res = INT_MIN;
- iterate through
nums:for(const int& val : nums) {
- if
valis 0, then setMin=1andMax=1, BUT DO NOT updateresif (val == 0) { Max = 1; Min = 1; res = max(val, res); }
- else update
MaxandMinin such way that they includeval. then update thereselse { int NewMax = max(Max * val, Min * val); NewMax = max(NewMax, val); int NewMin = min(Min * val, Max * val); NewMin = min(NewMin, val); Max = NewMax; Min = NewMin; res = max(Max, res); }
- if
return res
- initialize
- time - O(N), space - (1):
- Find Minimum in Rotated Sorted Array
- Search in Rotated Sorted Array
- 3Sum
- this solution is not efficient at all (beats 12% runtime and 13% memory), i will probably come back to this problem later
- time - O(N^2 Log N), space - O(N)
- sort
nums - remove duplicates from
nums:- each value can be repeated not more than 3 times
- ex. [0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,-1,-1,-1] -> [0,0,0,1,1,1,-1,-1,-1]
res_set = set()- iterate
first_val_inx(0 ... len(nums))- iterate
second_val_inx(first_val_inx+1 ... len(nums))- target = -(nums[first_val_inx] + nums[second_val_inx])
- find target using binary search
- if target is found:
- add triplet to
res_set
- add triplet to
- iterate
- sort
- Container With Most Water
-
Binary
- Sum of Two Integers
- time - O(1), space - O(1):
- for
0b01(1) and0b10(2) we can just usebitwise ORto get their sum :0b01 OR 0b10 = 0b11 (= 3) - however, if at least one bit is enabled both in
aandb, we can't usebitwise OR, because in this case we loose bits. For example, it doesn't work with0b101(5) and0b001(1), which sum should be 6:0b101 OR 0b001 = 0b101 (= 5)- 5 isn't correct answer
- so, to solve this problem, we need to rewrite
aandbin such way that their sum gives samesumbut no same bits enabled. e.g.0b010(2) and0b100(4), which sum is also 6:0b010 OR 0b100 = 0b110 (= 6)- now we got correct answer
- but how can we modify
aandbin such way that they don't have same enabled bits?- while
aandbhave at least one common enabled bitwhile ((a & b) != 0) {
- we get bits that are enabled only in
aor only inbint tmp = a ^ b; - we get common enabled bits and move them left (
0b1 + 0b1 = 0b10)b = (a & b) << 1; - and don't forget to update
a:a = tmp;
- while
- and when
aandbdon't have common enabled bits, we can return the answer:return a | b;
- for
- time - O(1), space - O(1):
- Number of 1 Bits
- Counting Bits
- Missing Number
- Actually used brute force approach: just sorted array and iterated until found a gap.
- Reverse Bits
- Don't forget that to set last bit of 32-bits signed integer you need to do
num |= 1 << 31, NOTnum |= 1 << 32. And to set first bit ->num |= 1 << 0.
- Don't forget that to set last bit of 32-bits signed integer you need to do
- Sum of Two Integers
-
Dynamic Programming
- Climbing Stairs
- Coin Change
- time - O(amount * coins.length), space - O(amount)
- create
best_qty = [amount+1] * (amount+1) best_qty[0] = 0iterate cur_amount in range 0..amountiterate through coinsbest_qty[cur_amount] = min(best_qty[cur_amount], best_qty[cur_amount-coin] + 1)
return best_qty[amount] > amount ? -1 : best_qty[amount];
- create
- time - O(amount * coins.length), space - O(amount)
- Longest Increasing Subsequence
- O(n^2) Bottom Up:
- dp[i] = longest combination for nums[i:]
- to get longest combination for nums[i:] we iterate through dp[:i+1] and nums[i:] to get max dp for num that is bigger than current (if nums[j] > curr then dp[i] = max(dp[i], 1+dp[j]))
- e.g. we have nums=[1,10,3,1,2]
- i = 4 (nums[i]=2)
- dp[4] = 1
- i = 3 (nums[i]=1)
- dp[3] = 2 (because nums[3] < nums[4])
- i = 2 (nums[i]=3)
- dp[2] = 2
- i = 1 (nums[i]=10)
- dp[1] = 1 (because nums[i+1:] are bigger than nums[i])
- i = 0 (nums[i]=1)
- dp[0] = 2
- i = 4 (nums[i]=2)
- and then we return max(dp)
- dp[i] = longest combination for nums[i:]
- time - O(N log N), space - O(N)
- O(n^2) Bottom Up:
- Longest Common Subsequence
- O(n*m) Bottom Up:
- Make matrix
nxm. Find best solution fortext1[n-1:]andtext2[m-1:], then totext1[n-2:]andtext2[m-1:], ... - If
text1[a]==text2[b], then best solution fortext1[a:]andtext2[b:]is equal to 1 + best solution fortext1[a+1:]andtext2[b+1:] - Otherwise, the best solution is MAX(best for
text1[a+1:]andtext2[b:], best fortext1[a:]andtext2[b+1:])
- Make matrix
- O(n*m) Bottom Up:
- Word Break
- O(n*m*n) Top Down:
- If we know that we can reach
s[i]with words from wordDict, then we find alls[n](n>i) that we can reach with words from wordDict and setdp[n]=true - Make
dp[len(s)+1]. Setdp[0]=0. - Iterate
i in range(0, len(s))- if
dp[i] == true- iterate through wordDict and if
s[i:].startswith(wordDict[j]), then setdp[i + len(wordDict[j])] = true
- iterate through wordDict and if
- if
- Return
dp[len(s)+1]
- If we know that we can reach
- O(n*m*n) Bottom Up:
- If
s[i].startswith(wordDict[j])anddp[i+len(wordDict[j])] == true, thendp[i] == true
- If
- O(n*m*n) Top Down:
- Combination Sum IV
- O(Target*N) Top Down:
- Step by step find best solution for 0..Target (
CurrentTarget) and save it todpMap - So, to calculate
dp[i]you can use previous valuesdp[i] += dp.get(CurrentTarget - Num, 0) for Num in Nums
- Step by step find best solution for 0..Target (
- O(Target*N) Top Down:
- House Robber
- O(n): Overall approach:
dp = nums- for i in range (len(nums) + 2)
dp[i] += max(dp[i-2], dp[i-3])
- result is
dp[-1]
- O(n): Overall approach:
- House Robber II
- O(n):
- Use previous problem to solve this one
Result = max(original_rob(Nums[1:]), original_rob(Nums[:-1]))
- O(n):
- Decode Ways
- O(n):
- pseudo code:
for i in range(0, len(str)): # this is general case (think about first letters) dp[i] += dp[i-2] if i >= 2 and 1 <= int(s[i-1] + s[i]) <= 26 else 0 dp[i] += dp[i-1] if s[i] != '0' else 0
- idea:
- if s[i] != '0', then it can represent letter, so to dp[i] we can add all variants of encoding s[:i] (because there is one way to get from s[i-1] to s[i])
- if 1 <= int(s[i-1] + s[i]) <= 26, then current and previous numbers can represent letters, so to dp[i] we can add all variants of encoding s[:i-1] (because there is one way to get from s[i-2] to s[i])
- pseudo code:
- O(n):
- Unique Paths
- O(n * m):
- pseudo code:
for M in range(m): for N in range(n): dp[N][M] = dp[n-1][m] + dp[n][m-1] return [n-1][m-1]
- idea:
- for every cell in first row number of paths is equal to one, so we make list [1] * n
- for next rows: number of paths is equal to number of paths for upper cell + number of paths for left cell
- return number of paths for last cell
- pseudo code:
- O(n * m):
- Jump Game
- O(n):
- idea:
- while iterating through array, update
max_inxvalue which stores maximum index you can reach. ifi > max_inx, then return false. ifmax_inx >= len(nums)-1, return true.
- while iterating through array, update
- idea:
- O(n):
-
Graph
- Clone Graph
- If
s == NULL, then just return NULL - Make
struct Node* arr[101]to store all processed nodes - Then iterate through neighbors nodes starting from s (DFS):
- If node is already in
arr, then return it - Otherwise, add node to
arrand start to iterate though its neighbours...
- If node is already in
- If
- Course Schedule
- Make a dict
PreMapand use course for key and list of prerequisites for value - Make set
visited dfsalgorithm:- If course in
visited, then return False (because that means that we are in loop which means that to finish course you need to finish this course before which is impossible) - If course in
PreMapand prerequisites is empty list, then return True - Add course to
visitedand rundfsthrough each prerequisites of current course.- If at least one returns False -> also return False
- Remove current course from
visited - Set
PreMap[course]=[](we already checked that course, so no need to check it again)
- If course in
- Run
dfsfor i in range(0, numCourses)
- Make a dict
- Pacific Atlantic Water Flow
- Start from Pacific borders and iterate like this:
- If
rnot out of boundaries ANDcnot out of boundaries AND(r,c)not inCanReachPacificANDheights[r][c]<=heights[prev_r][prev_c]- Add
(r,c)toCanReachPacific - Iterate through
(r+1,c) - Iterate through
(r-1,c) - Iterate through
(r,c+1) - Iterate through
(r,c-1)
- Add
- If
- Repeat same thing with Atlantic
- Find
CanReachPacificandCanReachAtlanticintersections and return them
- Start from Pacific borders and iterate like this:
- Number of Islands
- with all r (0..gridSize) and c (0..gridColSize) combinations:
- if grid[r][c] == 1 ->
- res += 1
- Set all cells of island to zero, so, we won't explore this island second time.
- if grid[r][c] == 1 ->
- with all r (0..gridSize) and c (0..gridColSize) combinations:
- Longest Consecutive Sequence
- Make set
numsSetwithnums - Iterate through
numsSetand find begginging of sequence- Set
lengthto zero - Start
whileloop (while num+length in numsSet)- length++;
- Compare best length with this length
- Return best length
- Set
- Make set
- Alien Dictionary (Premium)
- Make a dict[char: set], where set includes
charsbigger thanchar. e.g. if["ab", "ac"], then dict should be{"b": {"c"}} - Use post-order DFS to get the result
- Iterate through nodes until found graph with no further nodes. Add it to res. Then add its parents to res, then their parents...
- After iteration is done, reverse the result list
- IMPORTANT NOTE: if loop if found, then algorithm should return ""
- return reversed result list
- Make a dict[char: set], where set includes
- Graph Valid Tree (Premium)
- There are 2 conditions for valid Tree:
- Graph should be interconnected
- Graph should not contain loops
- Make dict
adjfor eachedgeinedges:adj[edge[0]].append(edge[1])adj[edge[1]].append(edge[0])
- make
visited set - Dfs:
- if
elementinvisited:return False(loop detection!) visited.add(element)- iterate through
neighbors(throughadj[element])- if
neighbor!=prev_dfs_val- Explanation of "
neighbor!=prev_dfs_val":prev_dfs_val(the edge from which we came to current edge) is in visited, so, if we run dfs forprev_dfs_valagain, we will get false-positive loop detection and return False - if
(dfs(element=neighbor, prev_dfs_val=element, visited, nodes) == False):return False
- Explanation of "
- if
- return True
- if
- Note: the main function should call
dfsonly once, because if graph is interconnected, then you can find ALL edges starting from any edge in graph. For example:-
Node1 - Node2 - Node4 - Node5 - Node3 # from any node i can get to any node. For example, let's start from Node5: - Node5->Node4->Node2->Node1->Node3 -
Node1 - Node2 - Node4 - Node5 - Node3 Node7 - Node8 # we never can get to Node3 if we start from Node7 or Node8,
-
- return dfs(0, -1, visited, nodes) && n == len(visited)
- Explanation: during dfs we find loops (if element was visited twice -> return False) and by
n == len(visited)we check that graph is interconnected (if graph isn't interconnected ->dfswon't visit all edges)
- There are 2 conditions for valid Tree:
- Number of Connected Components in an Undirected Graph (Premium)
- Very similar to Number of Islands
- O(V + E) (V - num of vertices, E - num of edges)
- Important note! The node may NOT BE PRESENT IN edges. So, this situation is possible:
edges=[0,1], n = 4, it can be illustrated like this:Node0 - Node1 Node2 Node3 Node4 - So,
adj={i: [] for i in range(n)} - Then we fill
adj.for edge in edges:adj[edge[0]].append(edge[1])adj[edge[1]].append(edge[0])
- DFS algorithm (
args: int i, dict[int,list[int]] adj):- Explanation: when we get i, we delete all nodes connected with i
- If
i not in adj(this node was already explored) neighbors = adj.pop(i)for neighbor in neighbors:DFS(neighbor, adj)
res = 0for i in range(0, n):if i in adj:(if this group of connected nodes wasn't explored)res+=1DFS(i, adj)(remove all connected nodes)
return res
- Important note! The node may NOT BE PRESENT IN edges. So, this situation is possible:
- Clone Graph
-
Interval
- Insert Interval
- O(n):
-
Input interval consists of 3 parts (but sometimes not all parts are present):
Part1 Part2 Part3 Condition Intervals[i][1] < NewInterval[0]NOT Part1 AND NOT Part3NewInterval[1] < Intervals[i][0]Action res.add(Intervals[i])NewInterval = [min(NewInterval[0], Interval[i][0]),
max(NewInterval[1], Interval[i][1])]res.add(NewInterval), RETURNres + Intervals[i:]Explanation We need all intervals that are smaller than NewInterval, so add all of them We merge overlapping intervals We insert NewInterval just before the part where all intervals are bigger than NewInterval
- Possible optimizations:
- It is possible to solve this problem in-place (not create new list, but update input list). It would be O(1) space complexity
- Also, with binary search it is possible to find all three parts. It would be O(log n) time complexity.
- O(n):
- Merge Intervals
- time - O(Nlogn), space - O(N)
- Sort input
intervals - Initialize integers
Start, End=interval[0][1], interval[0][1] - Iterate through
intervals:- if
intervals[i][0] <= Start->End = MAX(End, intervals[i]) - else -> add
[Start, End] to res, setStart, End = intervals[i][0], intervals[i][1]
- if
- add
[Start, End] to res - return
res
- Sort input
- time - O(Nlogn), space - O(N)
- Non-overlapping Intervals
- time - O(N log N), space - O(1)
- Sort input
intervals - Save
prev_end = intervals[0][1] - Iterate through sorted
intervals[1:]:- if intervals[i][0] >= prev_end (if doesn't overlap with prev interval):
prev_end = intervals[i][1]
- else (interval overlaps with prev interval):
res += 1(need to remove some interval in any case)prev_end = min(prev_end, intervals[i][1])(save interval with smallestprev_end, because it will overlap with smaller number of intervals)
- if intervals[i][0] >= prev_end (if doesn't overlap with prev interval):
- Sort input
- time - O(N log N), space - O(1)
- Meeting Rooms (Premium)
- time - O(N log N), space - O(1):
- Sort
intervalsbystart - Iterate through
intervals- If
prev interval endis bigger thancurrent interval start->return false
- If
return true
- Sort
- time - O(N log N), space - O(1):
- Meeting Rooms II (Premium)
- time - O(N log N), space - O(n):
- Sort
intervalsbystart - Create empty list
last_elementsto store the end of last meeting for day. Keeplast_elementssorted - Iterate through
intervals:- If
intervals[i].startis smaller than smallest element inlast_elements:- We can't put this meeting in existing days. We need one more
- We add
intervals[i].endtolast_elements. Keeplast_elementssorted!
- Else:
- We can put this meeting in existing day
- We move the end of last meeting for day:
- Remove end of last meeting for this day.
- We add
intervals[i].endtolast_elements. Keeplast_elementssorted!
- If
- Sort
- For example, we have intervals
[(25,579),(218,918),(1281,1307),(623,1320),(685,1353),(1308,1358)]:i = 0, intervals[i].start=25, intervals[i].end=579, last_elements: # we add end of first meeting: i = 1, intervals[i].start=218, intervals[i].end=918, last_elements: 579 # because of intervals[i] starts before 579, we need one day more: i = 2, intervals[i].start=623, intervals[i].end=1320, last_elements: 579, 918 # We can schedule meeting for existing day which last meeting ends at 579 index to delete = 0 i = 3, intervals[i].start=685, intervals[i].end=1353, last_elements: 918, 1320 i = 4, intervals[i].start=1281, intervals[i].end=1307, last_elements: 918, 1320, 1353 index to delete = 0 i = 5, intervals[i].start=1308, intervals[i].end=1358, last_elements: 1307, 1320, 1353 index to delete = 0
- time - O(N log N), space - O(n):
- Insert Interval
-
Linked List
- Reverse Linked List
- Each
valueof reversed list node isvalueof head, eachnext- previously accumulated reversed list
- Each
- Detect Cycle in a Linked List
- time - O(N), space - O(N):
- find duplicates by set
- time - O(N), space - O(N):
- Merge Two Sorted Lists
- Merge K Sorted Lists
- time - O(k log k), space - O(k):
- Note: in C++
priority_queuedata structure should be used for efficiency - Filter out all
nullvalues fromlists - Sort all nodes in
lists(ifpriority_queueis used, then sort in descending order) and save them tomin_heap - While
listsis not empty:- to last node of
resadd smallest element ofmin_heap - remove smallest element of
min_heap - add
nextnode of removed element ifnextnode is notnull
- to last node of
- return
res
- Note: in C++
- time - O(k log k), space - O(k):
- Remove Nth Node From End of List
- time - O(N), space - O(N):
- save all of nodes in array
- get the nodeToRemove (
arr[index_of_last_node - (n - 1)]) - get the nodeToUpdate (
arr[index_of_last_node - (n - 1) - 1]) - set nodeToUpdate->next = nodeToRemove->next
- but there is also EDGE CASE: if nodeToRemove is
head, then we just should returnnextof thehead
- time - O(N), space - O(N):
- Reorder List
- time - O(N), space - O(1):
- Get the first node of second half
- Reverse second half
- Iterate through first and second half to update nexts
- time - O(N), space - O(1):
- Reverse Linked List
-
Matrix
-
- time - O(n*m), space - O(1)
- use first row and first col to save if the row or the col should be set to 0
- time - O(n*m), space - O(1)
-
- time - O(n*m), space - O(n*m)
- add first row of matrix to
spiraland delete first row - add right col of matrix to
spiraland delete right col - add last row of matrix to
spiraland delete last row - add left col of matrix to
spiraland delete left col
- add first row of matrix to
- time - O(n*m), space - O(n*m)
-
- time - O(N^2), space - O(1)
- Process each layer one by one
- Process each val in layer:
- save top val
- top <- leftc
- left <- bottom
- bottom <- right
- right <- top
- Process each val in layer:
- Process each layer one by one
- time - O(N^2), space - O(1)
-
- time - (r * c * l^4) , space - (r * c)
- Use
dfs- if
rorcis out of boundaries -> return false - if
r;cinside currentpath-> return false - if
board[r][c] != word[word_ind]-> return false - if
word_ind == word.len() - 1-> return true - add
r;ccoordinate topath - explore neighbors using
dfs. if one of calls returns true -> return true - remove
r;cfrompath
- if
- iterate through whole board using dfs, until
true - if none returned
true-> returnfalse
- Use
- time - (r * c * l^4) , space - (r * c)
-
-
String
- Longest Substring Without Repeating Characters
- note: i solved the problem using
C++andC. MyCsolution has more efficient algorithm. Here I describe algorithm I applied forCsolution. - BE CAUTIOUS: in my first implementation i made mistake - i did not consider case when one substring can be part of two different substrings. For example in word "mewmkz" the substring "ewm" can be part of "mewm" and part of "ewmkz", however, "ewmkz" is bigger.
- time - O(N), space - O(1):
- create array
chars = [-1] * 128to store indexes of last occurances of chars left = 0maxLen = 0for right in range(len(s)):cur_char = s[right]if chars[cur_char] >= left(ifcur_charis betweenleftandright-> it is inside current word)- since we don't need char duplicates, we need to find biggest substring of current substring which DOES NOT include cur_char
left = chars[cur_char] + 1(moveleftto first char after last occurance ofcur_char)
maxLen = max(maxLen, right - left + 1)char[cur_char] = right(save last occurance of char)
return maxLen
- create array
- note: i solved the problem using
- Longest Repeating Character Replacement
- *time - O(len(s)26), space - O(26)
- create set
all_charswith all unique chars ofs - iterate through each
charinall_chars:- using slow and fast pointer find best solution for
char
- using slow and fast pointer find best solution for
- return biggest solution
- create set
- *time - O(len(s)26), space - O(26)
- Minimum Window Substring
- Save number of each letter in
t - Iterate through
s:- save last usage of char
s[i] - if enough chars for
t:- according to last usages of chars calculate len of current window
- if needed -> update best len of window
- save last usage of char
- Save number of each letter in
- Valid Anagram
- Group Anagrams
- time - O(N log(N) + N * M log(M)), space - O(N * M) (N - len of strs, M - len of strs[i])
- Sort every string in
strs - Sort
strs - If strs[i] == strs[i-1], then they are in same group
- Sort every string in
- time - O(N log(N) + N * M log(M)), space - O(N * M) (N - len of strs, M - len of strs[i])
- Valid Parentheses
- Valid Palindrome
- time - O(N), space - O(1)
- create
leftandrightpointers - move pointers to center, but don't forget to ignore non-alphanumeric chars and convert into lowercase before comparing
- create
- time - O(N), space - O(1)
- Longest Palindromic Substring
- time - O(N^2), space - O(1)
- There are 2 types of palindromes: odd ("zaz") or even ("issi) length
- For every point in
stry to create odd and even palindrome.- If s[l] == s[r] -> res++, and then:
- save best len and value of left pointer (right pointer can be calculated by best len and best left)
- expand palindrome (
l--,r++) - repeat
- If s[l] == s[r] -> res++, and then:
- time - O(N^2), space - O(1)
- Palindromic Substrings
- time - O(N^2), space - O(1)
- There are 2 types of palindromes: odd ("zaz") or even ("issi) length
- For every point in
stry to create odd and even palindrome.- If s[l] == s[r] -> res++, and then expand palindrome (
l--,r++) and repeat
- If s[l] == s[r] -> res++, and then expand palindrome (
- time - O(N^2), space - O(1)
- Encode and Decode Strings (Premium)
- time - O(N), space - O(N)
- use this notation:
len of word+#+wordlen of word1+#+word1
- e.g.
2#ee4#jaja
- use this notation:
- time - O(N), space - O(N)
- Longest Substring Without Repeating Characters
-
Tree
- Maximum Depth of Binary Tree
- Same Tree
- time - O(N), space - O(N)
- use dfs:
- if
porqis null &&p!=q, then return false - else return
p->val == q->val&&dfs(q->left, p->left)&&dfs(q->right, p->right)
- if
- use dfs:
- time - O(N), space - O(N)
- Invert Binary Tree
- Binary Tree Maximum Path Sum
- time - O(N), space - O(N)
- concept:
- dfs(node):
- update res if one of these vals is bigger than res:
- node
- left + node + right # NOTE that
dfscan't returnleft + node + right, becauseparent + node + node.left + node.rightis impossible - left + node
- node + right
- return best(node, left + node, right + node)
- update res if one of these vals is bigger than res:
- dfs(node):
- DON'T FORGET ABOUT CASE
[-3]. I recommend to set initialrestoroot->val
- concept:
- time - O(N), space - O(N)
- Binary Tree Level Order Traversal
- time - O(N), space - O(N)
- def traverse(node, curr_depth):
- add node->val to res[curr_depth]
- traverse(node->left)
- traverse(node->right)
- def traverse(node, curr_depth):
- time - O(N), space - O(N)
- Serialize and Deserialize Binary Tree
- time - O(N), space - O(N)
- serialization:
- BFS:
- write all vals of last row to res
- clear last row of nodes
- save all non-null lefts and rights as last row
- BFS:
- deserialization:
- BFS:
- keep last row of nodes
- update theirs lefts and rights of last row of nodex
- clear last row of nodes
- add lefts and rights
- BFS:
- serialization:
- time - O(N), space - O(N)
- Subtree of Another Tree
- Not very efficient solution, there should be smth better. Runtime - beats 6%, Memory - 93%
- time - O(N * M), space - O(N * M)
- 2 funcs:
compareif root == subRoot && compare(root->left, subRoot->left) && compare(root->right, subRoot->right) -> return true- if root or subRoot is Null -> stop and don't call next compare
exploreif root == subRoot -> compareif explore(root->left, subRoot) || explore(root->right, subRoot) -> return true
- 2 funcs:
- Construct Binary Tree from Preorder and Inorder Traversal
- time - O(N), space - O(1)
- create dict
mappingwhere keys are values and values are keys of elements frominorderinorder = [4,2,1]->mapping = {4:0, 2:1, 1:2}
preorderIndex = 0recurs func(start, end):if start > endreturn null
rootVal = preorder[preorderIndex]preorderIndex += 1(we move further and further)mid = mapping[inorder[rootVal]]root = Node(rootVal)root->left = recurs(start, mid)root->right = recurs(mid + 1, end)
return recurs(0, len(inorder))
- create dict
- time - O(N), space - O(1)
- Validate Binary Search Tree
- time - O(N), memory - O(N):
- for each node there is a minimum and maximum possible value, which depends on parents of node
- e.g. [5,3,10,null,8,null,null]
- OK for root, because 5 is inside boundaries (-inf, +inf). then, for left child max possible val is 4, for right child minimum possible val is 6
- OK for left child of root, because 3 is inside boundaries (-inf, 4]. for left child max possible val is 2, min possible val is -inf, for right child max possible val is 4, min possible val is 4
- OK for right child of root, because 10 is inside boundaries [6, +inf)
- NOT OK for root->left->right, because 8 is not inside boundaries [4, 4]
- time - O(N), memory - O(N):
- Kth Smallest Element in a BST
- time - O(N), space - O(k)
- create
priority_queue<int, vector<int>, greater<int>>(this type of queue returns vals in sorted order) - we add all nodes' vals to this queue
- get kth smallest element using queue
- create
- time - O(N), space - O(k)
- Lowest Common Ancestor of a BST
- time - O(height), space - O(height):
if (p->val > q->val)- swap(p, q)
- lowestCommonAncestor(root, p, q)
- if
node->valis smaller thanp(smallest val) andnode->right != nullptrnode->rightis also ancestor and it should be explored- lowestCommonAncestor(
node->right,p,q)
- if
node->valis bigger thanq(biggest val) andnode->left != nullptrnode->leftis also ancestor and it should be explored- lowestCommonAncestor(
node->left,p,q)
- else
node->leftandnode-rightare not ancestors and should not be explored- return root
- if
- time - O(height), space - O(height):
- Implement Trie (Prefix Tree)
- time - O(N), space - O(NL)
- Each Trie should have:
- Children: dict[char, Trie] = {}
- IsEnd: bool = false
- insert
- we need pointer to current Trie and pointer to iterate through word
int p = 0; Trie *cur = this;
- then iterate through all chars in
wordwhile (p < word.size()) {- if
curwas never continued by word[p] -> create corresponding Trieif ( cur->Children.find(word[p]) == cur->Children.end() ) { cur->Children[word[p]] = new Trie(); }
- update pointers:
cur = (cur->Children[word[p]]); p++;
- if
- save that this Trie is the end of the word
cur->IsEnd = true;
- we need pointer to current Trie and pointer to iterate through word
- search
searchis almost the same asinsert, but- if
curnever was continued byword[p]-> return false;if ( cur->Children.find(word[p]) == cur->Children.end() ) { return false; }
- in the end of func we should check that
curtrie is actually the end of word:return cur->IsEnd
- if
- startsWith
startsWithis almost the same assearch, but- we don't need to check that last Trie is end of word, so in the end of the func we just return true
return true
- we don't need to check that last Trie is end of word, so in the end of the func we just return true
- Each Trie should have:
- time - O(N), space - O(NL)
- Add and Search Word
- time - O(N∗26^M):
- almost as previous one but:
- during
searchifword[i]== '.', then just try to run search for word[i+1:] using cur->Children[...try all non-null...]
- during
- almost as previous one but:
- time - O(N∗26^M):
- Word Search II
- time - O(M∗(4∗3(L−1))), space - O(N):
- Main idea:
- create Trie tree for
words - iterate through board and using DFS check if any word from
wordscan be made up
- create Trie tree for
- Main idea:
- time - O(M∗(4∗3(L−1))), space - O(N):
-
Heap
- Merge K Sorted Lists
- time - O(Nlogk), space - O(K)
- create
priority_queue(e.g.heapqin python) - add nodes to
priority_queue - create
result_node(val=0) - save start of result sequence:
root=result_node - add
smallest_nodefrompriority_queuetoresult_nodeand addsmallest_node.nexttopriority_queue smallest_node=priority_queue.pop()# pop smallest elementpriority_queue.put(smallest_node.next)- return
root.next
- create
- time - O(Nlogk), space - O(K)
- Top K Frequent Elements
- time - O(N), space - O(N)
- calc frequency of each num
unordered_map<int, int> count; count.reserve(nums.size()); for (const auto& num : nums) { count[num]++; }
- add to buckets where index = frequency, vals = list of nums with such frequency
vector<vector<int>> bucket (nums.size() + 1); for (auto &p : count) { bucket[p.second].push_back(p.first); }
- iterate through bucket in reverse to get most frequent nums first. return res as soon there are enough vals:
for (int z = bucket.size() - 1; z >= 0; z --) { for (int j = bucket[z].size() - 1; j >= 0; j--) { if (res.size() == k) { return res; } res.push_back(bucket[z][j]); } }
- calc frequency of each num
- time - O(N), space - O(N)
- Find Median from Data Stream
- time - O(log n), space - O(N)
- to solve this problem we need to priority queues:
smaller,bigger.smaller.top()is biggest val fromsmaller,bigger.top()is smallest val frombigger.
- when we add new num, we add it according to these rules:
- Biggest val of
smallershould be always SMALLER than smallest val frombigger. - Difference between
smaller.size()andbigger.size()should be <= 1. if difference becomes bigger than 1 -> we rebalancesmallerandbiggerqueues, BUT DO NOT VIOLATE previous rule (biggest val ofsmallershould be always SMALLER than smallest val frombigger)
- Biggest val of
- how do we find median?
- if smaller.size() > bigger.size()
- median = smaller.top()
- if bigger.size() > smaller.size()
- median = bigger.top()
- else
- median = (smaller + bigger) / 2.0
- if smaller.size() > bigger.size()
- to solve this problem we need to priority queues:
- time - O(log n), space - O(N)
- Merge K Sorted Lists
I think I should go back and solve this questions again:
- Array
- 3Sum. Cheated for this question.
- Maximum Product Subarray
- Binary
- Sum of Two Integers. Cheated for this question.
- Dynamic Programming
- Coin Change
- Longest Increasing Subsequence
- Longest Common Subsequence. Cheated.
- Word Break. Cheaaated. However, i watched Bottom-Up solution, but implemented Top-down, so it's just half cheating
- Combination Sum IV. I cheated.
- Graph
- Course Schedule. Cheated, didn't even rewrite in C or Erlang.
- Pacific Atlantic Water Flow. Even after watched explanation by NeetCode spent about 2 hours to implement. I surely need to come back to this.
- Longest Consecutive Sequence
- Alien Dictionary (Premium)
- Graph Valid Tree (Premium)
- Interval
- Matrix
- String
- Tree
- Heap
- Top K Frequent Elements
- Find Median from Data Stream Also, I think I can find better approach for this problems:
- Missing Number
- String
- Yandex
- According to this article, these problems can be asked on interview:
- Interview on YouTube
- Designs. There are some cool deisgns, explore them, I think it will help on interviews.
- LRU cache
- Shuffle an Array
- ... explore and find some other cool designs to implement
- solve several algorithms with BFS, not DFS
TODO: also need to solve some divide and conquer problems
TODO: also need to solve some greedy problems
Solutions can be found in yandex_trainings directory
Explanations
- A_mushrooms
- O(n):
- find vasya's and masha's sum
- find vasya's mushrom with smallest weight and masha's mushroom with biggest weight
- if masha's biggest mushroom is bigger than smallest vasya's mushroom, then give mushroom to vasya
- O(n):
- 1_B_mother
- O(1)
- to solve this task you need dfs or bfs
- from current state you should try to any possible next_state (if next_state is not in path)
- if both items are at home -> time = min(best_time, time)
- state should consist of these elements:
- curr_location ('d', 'p', 's')
- items in hands
- items at home
- O(1)
- 1_C_cybersecurity
- O(n):
- you can't replace "a" with char "a", so for each "a" (or any other letter) the number of combinations is the same.
- number of combinations in "aabc" is (qty of a) * (qty of other letters), so for "a" it would be 2 * 2 (baac, abac, caba, acba). then do the same with "bc", then with "c"
- and don't forget to add 1 to res, because we also should include original password
- O(n):
- 1_D_contest
- take at least one problem from each theme
- take any problems until the len(res) == k
- 1_E_increment
- for some
z: ifn mod 10=z, then afterIiterationsn mod 10=zagain. You need to find frequency of such repeations and interval between first and second occurances to calculate the rest.
- for some
- 1_F_plus_minus_question
- Calculate biggest row sum and smallest col sum assuming that
?is+for rows and?is-for cols. However, NOTICE that ifgrid[i][j]=?, then 2 should be extracted fromdiff.
- Calculate biggest row sum and smallest col sum assuming that
- 1_G_5_sequence
- O(i*j):
- for each
(i,j)save:- vertical length
(curRow[j].vert = prevRow[j].vert + 1), - left to right diagonal length
(curRow[i].from_left_to_right = prevRow[i-1].from_left_to_right +1), - right to left diagonal length,
- horizontal length (using
curRow[i-1]). - If some
length == 5, then print Yes and stop program. Iflength == 5never happened -> print No
- vertical length
- for each
- O(i*j):
- 2_A_ball
- O(i):
steps[0] = 0, steps[1] = 1, steps[2] = 2, steps[3] = 4, steps[i] = steps[i-3] + steps[i-2] + steps[i-1]return steps[N]
- O(i):
- 2_B_river
- 2_C_intervals
- O(N * log(n))
- 2_D_dictionary
- O(len(word) * len(word_dict)):
- every time when
word[i:].startswith(word_dict[j]), we do thisdp[i + len(word_dict[j])].append(copy(dp[i][0]) + [j]). - e.g. we have input string "joja" and word_dict: "joj", "jo", "ja":
- dp = [[], [], [], [], []]
- i = 0 ("joja")
- j = 0 ("joj")
- "joja"[i:].startswith("joj"), so now dp becomes:
- dp = [[], [], [], [0], []]
- "joja"[i:].startswith("joj"), so now dp becomes:
- j = 1 ("jo")
- "joja"[i:].startswith("jo"), so now dp becomes:
- dp = [[], [], [1], [0], []]
- "joja"[i:].startswith("jo"), so now dp becomes:
- j = 2 ("ja")
- "joja"[i:].startswith("jo") is false, so we don't update dp
- j = 0 ("joj")
- i = 1 ("oja")
- there is no matching word from worddict for "joja"[i:]
- i = 2 ("ja")
- j = 2 ("ja")
- "joja"[i:].startswith("jo"), so now dp becomes:
- dp = [[], [], [1], [0], [1,2]]
- STOP HERE, because we already found solution: [1,2] ("jo ja")
- "joja"[i:].startswith("jo"), so now dp becomes:
- j = 2 ("ja")
- every time when
- Note: this can be solved O(len(word) * log(word_dict)) if we use binary search to find matching words from word_dict
- O(len(word) * len(word_dict)):
- 2_E_tower
- O(N):
- Solution can be divided in 2 steps:
- make array
towerswheretower[i]is safety of tower which last column isi - make array
dpwheredp[i][0]= best solution of 0..i:include = include = towers[i] + dp[i-K][0]exclude = sorted((val, prev_vals) for val, prev_vals in dp[i-K+1:i+1])[-1]- then we compare exclude and include and make decision
- make array
- Solution can be divided in 2 steps:
- O(N):
- 2_F_game
- O(N):
- if cur_line[i] is reachable:
- cur_line[i] = best_reachable_val_of_previous_raw + (1 if line[i] == 'C' else 0)
- else
- cur_line[i] = 0
- IMPORTANT: stop if next row in unreachable:
WW. .WW # this one is unreachable
- if cur_line[i] is reachable:
- O(N):
- 2_G_stairs
- O(N^3):
dp[0][0] = 1dp[total][first_row]= number of possible stairs, which consist oftotalcubes and which first row consists offirst_rowcubesdp[total][first_row] = dp[total-first_row][first_row-1] + dp[total-first_row][first_row-2] + ... + dp[total-first_row][1]. In this way we calculate all possible ways to place cubes when total sum istotaland there arefirst_rowcubes in first row- But remember that
first_row <= total
- O(N^3):
- 2_H_matchsticks
- O(1):
- if 1 matchstick is left, then person wins:
- he wins (can remove 1 last stick)
- if 2 matchsticks:
- win (can remove 2 last sticks)
- if 3 matchsticks:
- win (can remove 3 last sticks)
- if 4 matchsticks:
- loss (no matter how many matchsticks you take - opponent will win)
- if 5 or 6 or 7:
- win (because can remove sticks and get 4 which means that opponent will lose)
- if 8:
- loss (because can't remove enough sticks to get to number where opponent will lose)
- as we can see the if
N % 4 == 0, then person loses
- if 1 matchstick is left, then person wins:
- O(1):
- 2_I_chain
- 2_J_masquerade
- O(L * N * Fmax):
dp[s][k] = best(Cost(k ,t) + dp[s-t][k-1] for t in 0..F[k])- so we calculate best cost for
smeters andkth house using previous results
- so we calculate best cost for
- however, sometimes
tcan be >= thans. In that case we do this:dp[s][k] = min(dp[s][k], Cost(k, t))- so if the best variant is to just buy more or equal to
smeters in shopk, then buy it in shopk
- so if the best variant is to just buy more or equal to
- O(L * N * Fmax):
- 3_A_shelf
- 3_B_from_dead_end_to_dead_end
- i don't know complexity, but it's pretty fast:
- gradually simultaniously explore all nodes starting from leaf nodes. Example:
- i don't know complexity, but it's pretty fast:
- 3_C_advertisement
- O(log(n) * n):
- By binary search find matching k
- I used brute force algorithm to check if k matches. Since I used C++, I didn't really care about optimization of this thing 🙂
- O(log(n) * n):
- 3_D_currency_exchange
- sort
tablesand save values like this[ [val1, orig_idx1], [val2, orig_idx2] ]. in this way it would be easier to determine original indexes for answer. - iterate with
ithrough sorted tablestarget = P / val_i- with binary search find best
jfor thistargetWHICH IS NOT EQUAL TO i - if
val_i / val_jis smaller thanbest_ratio, then save original indexes of those values as best indexes
- print best indexes
- sort
- 3_E_feudalism
- 3_F_ancestor
- Iterate through nodes. Before entering node, save current time, after exploring node, also save time. Like this:
- After we explored all nodes we can easily determine if the node is parent or not.
2ndnode is child of1st, because 1-10 interval includes 2-3. However3rdnode is not parent of2nd, because 2-3 interval is not inside 4-9 interval.
- 3_G_gravity
- 3_H_pickup
- 3_I_expression_tree
- 3_J_interviews
- 4_A_office_trips
- Iterate through all seconds since 00:00 till 23:59
- Process 2 stations:
- If trip starts at current second:
- If any bus on current station, then use it AND don't forget to save that at arrival time on opposite station there should be +1 bus
- If no available bus on station, then total_buses += 1
- If there are buses left at current second, then in next second at current station they should be available
- If trip starts at current second:
- Process 2 stations:
- Iterate through all seconds since 00:00 till 23:59
- 4_B_car_tax
- O(m * log(n)):
- Iterate through cars (
qvals):- with binary search find index of greatest
b(power) that is less thanq(car's power) - take t (
tax) for this index and multiply it byq - print result of multiplication
- with binary search find index of greatest
- Iterate through cars (
- O(m * log(n)):
- 4_C_candidates_queue
- Use Fenwick tree
- 4_D_friendship_won
- 4_E_repair_patholes
- 4_F_train
- 4_G_series_planning
- 4_H_boss_bonus
- 4_I_banner
- 4_J_autosport
- 2_C intervals was hard, it would be good practice to solve it again
More advanced sorting algorithms
Algorithms:
- Bubble Sort
- Insertion Sort List
- Selection Sort
- Heap Sort
- Quick Sort
- Merge Sort
Cool Article about data structures
Also, I think before implementing data structures it would be helpful to solve designs on leetcode
Data Structures:
- Linked list
- HashMap
- Queue









