Data Structures & Algorithms - 1
Data Structures & Algorithms - 1
JULY
Algorithms Guide
Resources, Notes, Questions, Solutions 2021
As with all 30DaysCoding resources, this guide is provided to you with the mission of
making the world’s resources more accessible to all.
Enjoy!
30DaysCoding.com
Table of Contents
Table of Contents.........................................................................................................................................2
Complete Data structures and Algorithms Roadmap....................................................................................3
Our Aim..................................................................................................................................................... 3
Practice...................................................................................................................................................... 3
Arrays...........................................................................................................................................................4
Introduction............................................................................................................................................... 4
Hash maps, tables...................................................................................................................................... 4
2 Pointers.....................................................................................................................................................5
Linked List....................................................................................................................................................9
Sliding Window..........................................................................................................................................13
Binary Search.............................................................................................................................................18
Recursion...................................................................................................................................................25
Backtracking...............................................................................................................................................32
BFS, DFS.....................................................................................................................................................40
Dynamic Programming...............................................................................................................................52
Trees..........................................................................................................................................................63
Graphs........................................................................................................................................................70
Topological Sorting.....................................................................................................................................80
Greedy Algorithms.....................................................................................................................................85
Priority Queue............................................................................................................................................88
Tries...........................................................................................................................................................92
Additional Topics........................................................................................................................................96
Kadane’s algorithm.......................................................................................................................................96
Djikstra’s algorithm.......................................................................................................................................97
AVL Trees................................................................................................................................................. 98
Sorting..................................................................................................................................................... 99
More........................................................................................................................................................ 99
Additional Awesomeness...........................................................................................................................99
2
30DaysCoding.com
We’ve covered all the amazing data structures and algorithms that you will ever need to study to get
that next opportunity. Hopefully, this will make you a better problem solver, a better developer, and
help you ace your next technical coding interviews. If you have any questions along the way, feel free to
reach out to us on 30dayscoding@gmail.com.
Our Aim
- Our aim is to help you become a better problem solver by gaining knowledge of different data
structures, algorithms, and patterns
- We want you to understand the concepts, visualize what’s going on, and only then move
forward with more questions
- Most phone interviews require you to be a good communicator and explain your approach even
before you write the solution. So it’s important to understand the core concepts and then work
on extra stuff.
⭐ ⭐ ⭐ There are thousands of questions which you can solve out there, but computer science and
coding is much more than just doing data structures. Building and developing something is the core of
computer science, so if you’re actually interested - then most of your time should go in learning new
frameworks and building stuff. Another important aspect of coding and life in general is to explore! The
more you explore -> the closer you get to your interests, so keep exploring and learning. Good luck!
Practice
- Practicing 150-200 questions will make you confident enough to approach any new problem.
This is just solving only 2 questions for 75 days, which is not a lot if you think about it!
- Being consistent is the key. Finish this guide in 75-90 days. Don’t rush it from today, take your
time, revisit topics after a while, read and watch a lot of videos, and eventually be
comfortable with problem solving!
3
30DaysCoding.com
- Enjoy the process and start today. I’m sure you’ll do great. Have fun.
Arrays
Introduction
⭐ Informally, an array is a list of things. It doesn’t matter what the things are; they can be numbers,
words, apple trees, or other arrays. Each thing in an array is called an item or element. Usually, arrays
are enclosed in brackets with each item separated by commas, like this: [1, 2, 3]. The elements of [1, 2,
3] are 1, 2, and 3.
- Introduction to Arrays
- https://www.cs.cmu.edu/~15122/handouts/03-arrays.pdf
- An Overview of Arrays and Memory (Data Structures & Algorithms #2)
- What is an Array? - Processing Tutorial
Arrays are used with all different types of data structures to solve different problems, so it’s kind of
hard to come up with array questions with just an array logic. Let’s discuss some of the most famous
patterns which concern arrays most of the time.
2D matrices are also arrays and are very commonly asked in interviews. A lot of graph, DP, and search
based questions involve the use of a 2D matrix and it’s important to understand the core concepts
there. We’ve discussed the common patterns in each section below so make sure to check that out.
4
30DaysCoding.com
Resources
Questions
- 1. Two Sum
- 771. Jewels and Stones
- Leetcode : How Many Numbers Are Smaller Than the Current Number
- Partition Labels
2 Pointers
⭐ For questions where we’re trying to find a subset, set of elements, or something in a sorted array -> 2
pointers approach is super useful.
5
30DaysCoding.com
Some common questions with this approach are concerned with splitting something or finding
something in the middle, eg: middle element of the linked list. This is something which you’ll recognize
instantly after solving some questions on it, so just try to see the template and start solving.
Here’s a general code template for solving a 2 pointer approach. We move from the left and right with
different conditions until there’s something we want to find.
6
30DaysCoding.com
}
return false;
}
Let’s start from the beginning, if the number is something other than the ‘value’, let’s just bring that to
the beginning? And then finally return the pointer.
Think of this -> we want to shift the elements behind if they don’t match the value given.
7
30DaysCoding.com
However, the logic would be more complex if the array is not sorted. We can simply store the elements
in a hashmap as we go, and eventually return when we find target-nums[i] in the array as we’re going
forward.
while (i < j) {
if (A[i] + A[j] == X)
return true;
8
30DaysCoding.com
Read ‡ ,)<›<‹ˇ‘
- Article: 2 Pointer Technique
- Hands-on-Algorithmic-Problem-Solving: 2 Pointers
Videos -·]
- How to Use the Two Pointer Technique
- Two Pointer | Is Subsequence | LeetCode 392.
Questions ӳ◆◆
- Middle of the Linked List
- 922. Sort Array By Parity II
- Reverse String
- Valid Palindrome
- E26. Remove Duplicates from Sorted Array
- 75. Sort Colors
- 11. Container With Most Water
Linked List
Introduction
⭐ Linked list is a data structure which stores objects in nodes in a snake-like structure. Instead of an
array (where we have a simple list to store something) we have nodes in the linked list. It’s the same
thing though, you can store whatever you want - objects, numbers, strings, etc.
The only difference is in the way it’s represented. It’s like a snake: with a head and tail, and you can
only access one thing at a time - giving its own advantages and disadvantages. So if you want to access
the 5th thing, you can’t do linked_list[5], instead -> you would have to iterate over the list from the
beginning and then stop when the number hits 5.
9
30DaysCoding.com
1
30DaysCoding.com
}
return false;
}
This solution is absolutely correct, but it requires you to have a separate set (space complexity), can we
do something better? Think of something on the lines of 2 pointers. Can we have a slow and fast potiner
where the fast tries to catch the slower one? If it can, we find a cycle, if not -> there’s no cycle.
Using slow and fast pointers:
while(
Even simpler:
head!=null && slow.next !=null
while(runner.next!=null
&& fast.next!=null && &&runner.next.next!=null)
fast.next.next != null)
Problem 3:{ Deleting
{ slow walker = awalker.next;
= slow.next;
node
⭐ We can runner
simply delete= arunner.next.next;
node by moving the pointer from the previous node to the next node.
fast = fast.next.next;
if(walker==runner) return true;
prev.next = node.next.
if(slow The onlyreturn
== fast) simple catch is that we have to iterate to reach the ‘node’ and there is
}
no instance given
true; to us.= head.next;
head
}
Part II
What if we want to delete a node without the head? You cannot iterate to reach that node, you just
have the reference to that particular node itself.
1
30DaysCoding.com
There’s a catch here -> we can’t delete the node itself, but change the value reference. So you can
change the next node’s value to the next one and delete the next one. Like this:
node.val = node.next.val;
node.next = node.next.next;
1. One simple way would be to compare every 2 lists, call this function, and keep doing until we
have a bigger list. Any other shorter way?
2. We can be smart about it and add all the lists into one big array or list -> sort the array ->
then put the elements back in a new linked list!
1
30DaysCoding.com
3. Or maybe we can use something called the priority Queue. We can add all the items to the
queue, take them out one by one and then store them in a new list. It will behave like a
normal queue or list, but save us a lot of time (complexity wise). Here’s more about it. Here’s
the priority queue solution: here.
Read ,)<‹ˇ‘‡›
- Linked list: Methods
- How I Taught Myself Linked Lists. Breaking down the definition of linked list
- Introduction to Linked List
Videos -]·
- Data Structures: Linked Lists
- Interview Question: Nth-to-last Linked List Element
Questions ◆ӳ
- 141. Linked List Cycle (Leetcode)
- Delete Node in a Linked List"
- 19. Remove Nth Node From End of List
- Merge Two Sorted Lists
- Palindrome Linked List
- 141. Linked List Cycle (Leetcode)
- Intersection of Two Linked Lists
- Remove Linked List Elements
- Middle of the Linked List
- lc 23. Merge k Sorted Lists
-
Sliding Window
1
30DaysCoding.com
Introduction
⭐ This is super useful when you have questions regarding sub-strings and sub-sequences for arrays
and strings. Think of it as a window which slides to the right or left as we iterate through the
string/array.
Sliding window is a 2 pointer problem where the front pointer explores the array and the back pointer
closes in on the window. Here’s an awesome visualization to understand it more: Dynamic Programming
- Sliding Window
1
30DaysCoding.com
- We store the sum for every window and then return the max at the very end.
int max_sum = 0;
⭐ int
A must need article which covers more about this topic: Leetcode Pattern 2 | Sliding Windows for
window_sum = 0;
Strings | by csgator sum
/* calculate | Leetcode
of 1stPatterns
window
*/ for (int i = 0; i < k; i++)
{
Problemwindow_sum
2: Fruits into
+= basket
arr[i];
} Fruit Into Baskets
904.
Super interesting
/* Start theproblem,
windowlet’s learn
from something
the left (k coolinstead
from it. This
of is a sliding window problem where we
want to keep
0)*/ for the
(intmaximum
i = k;of i2 unique fruits at{ a time.
< n; i++)
- window_sum
We begin with+= arr[i]start
2 pointers, - arr[i-k];
and end. // remove the left and add the right
max_sum = max(max_sum, window_sum); // store the maximum
} - We move the end pointer when we’re exploring stuff in the array -> this is the fast pointer
moving ahead.
- We move the start pointer only when we’re shrinking the window.
⭐ Think of this as expanding the window and shrinking it once we go out of bounds.
1
30DaysCoding.com
Now, how do we expand or shrink here? No clue to be honest, bye. Haha kidding, let’s do it.
We expand when we’re exploring, so pretty much always when we add an element to our search
horizon - we increase the end variable. Let’s take this step by step.
y)S We have 2 pointers, start/end, and a map -> we add elements with the ‘end’ pointer and take out
elements with the start pointer (shrinking)
while(end< tree.length){
int take
y) S Let’s a =thetree[end];
end pointer till the array length, add the element to the map and then while the
map.put(a, map.getOrDefault(a,0)+1);
number of unique fruits are more than 2, remove the element from the map
if(map.get(a)==1)counter++;
end++;
while(counter>2){
Now, we int
# wanttemp
something = tree[start];
to store something
the maximum window size at all times -> after the second loop has exited and
} map.put(temp, map.get(temp)-1); # remove elem
we’re in the nice condition -> maximum 2 unique fruits. The conditions can be changed here easily
count if(map.get(temp)==0)counter--; # decrease
according to the number
counter of unique
start++; fruits or min/max
# increment start given to us.
}
Here’s the combined code:
1
30DaysCoding.com
while(end< tree.length){
int a = tree[end];
map.put(a, map.getOrDefault(a,0)+1);
if(map.get(a)==1)counter++;
end++;
while(counter>2){
int temp = tree[start];
map.put(temp, map.get(temp)-
1);
if(map.get(temp)==0)counter--
; start++;
}
len = Math.max(len, end-start);
}
return len;
⭐ ⭐ ⭐ A must need article which covers more about this topic: Leetcode Pattern 2 | Sliding Windows for
Strings | by csgator | Leetcode Patterns
Read ,)<›‹ˇ‘‡<
- Leetcode Pattern 2 | Sliding Windows for Strings | by csgator | Leetcode Patterns
- Sliding Window algorithm template to solve all the Leetcode substring search problems
Videos ] -·
- Sliding Window Technique + 4 Questions - Algorithms
- Sliding Window Algorithm - Longest Substring Without Repeating Characters (LeetCode)
- Minimum Window Substring: Utilizing Two Pointers & Tracking Character Mappings With
A Hashtable
Questions ◆ӳ
- Maximum Average Subarray I
- 219. Contains Duplicate II
- 904. Fruit Into Baskets
- 1004. Max Consecutive Ones III
- 76. Minimum Window Substring
1
30DaysCoding.com
Binary Search
Introduction
⭐ We use binary search to optimize our search time complexity when the array is sorted (min, max) and
has a definite space. It has some really useful implementations, with some of the top companies still
asking questions from this domain.
The concept is: if the array is sorted, then finding an element shouldn’t require us to iterate over every
element where the cost is O(N). We can skip some elements and find the element in O(logn) time.
Algorithm
⭐ We start with 2 pointers by keeping a low and high -> finding the mid and then comparing that with
the number we want to find. If the target number is bigger, we move right -> as we know the array is
sorted. If it’s smaller, we move left because it can’t be on the right side, where all the numbers are
bigger than the mid value.
1
30DaysCoding.com
// key is found
if (x == A[mid]) {
return mid;
}
1
30DaysCoding.com
else:
# Element is not present in the array
return -1
2
30DaysCoding.com
return left
- Sqrt(x)
Find the square root of the number given. We can skip iteration over all the numbers and can simply
take the middle and go left or right.
Once you understand the question, it’s trivial to think of a brute force problem: explore all the possible
font sizes and then see what fits at the end. Return that. Thinking a little more, we see that we have a
range (sorted) and we don’t really have to check for each font before choosing the maximum one. Shoot
-> it’s binary search.
def find_max_font():
2
30DaysCoding.com
max_font = 0
start, end = min_font, max_font
while start<=end:
mid_font = start + (end-start)//2
if font_fits(mid_font):
max_font = max(max_font,
mid_font) elif mid_font == 'too
big':
# move left if font is too
big end = mid
else:
# move right if font is too small
start =
mid return
This was an additional round (round 4), so they kept it on the easier side. Some things to keep in
mind while taking a tech interview:
- Be clear with your thoughts and communicate well.
- Ask questions, look for hints, and explain before writing code.
2
30DaysCoding.com
Potential solution: Let’s call that a pivot point, separate out both the arrays, and find the element in
both separately using binary search? Does this make sense? No? Email us 30DaysCoding@gmail.com
and let’s discuss it there.
Let’s say the nums[start] is less than the nums[mid] -> we get our new start and end -> the start and
mid. We get this condition:
So we just add one more condition to the already existing binary search conditions. We shift the start
and end pointers after we’ve discovered the subarray where we need to shift. Here’s the full code:
2
30DaysCoding.com
if (nums[mid] == target)
return mid;
Similar Patterns
⭐ ⭐ ⭐ There are other, advanced use cases of binary search where we want to find a minimum time or a
minimum space (and more). One catch with every binary search question is the limit from low to high ->
which isn’t trivial for those problems.
For instance, Leetcode : Minimum Number of Days to Make m Bouquets. We can make ‘m’ bouquets
and each one needs ‘k’ flowers. It doesn’t look like a problem which can be solved using binary search,
but it can be. We can often define a new function which does additional condition mapping for us and
then helps us find the middle.
Here’s a generic template and some awesome information to binary search questions and identify
problems where there is a limit defined. Binary search template.
Read
- Lecture 5 MIT : Binary Search Trees, BST Sort | Lecture Videos
- Binary search cheat sheet for coding interviews. | by Tuan Nhu Dinh | The Startup
- Binary Search Algorithm 101 | by Tom Sanderson | The Startup
2
30DaysCoding.com
Videos ·-]
- Introduction to Binary Search (Data Structures & Algorithms #10)
Questions ӳ◆
- Leetcode #704 Binary Search
- Leetcode #349 Intersection of Two Arrays
- Leetcode-First Bad Version
- Arranging Coins
- 35. Search Insert Position
- Leetcode #33: Search in Rotated Sorted Array
- 34. Find First and Last Position of Element in Sorted Array
- Leetcode #230 Kth Smallest Element in a BST
- Find Peak Element
- Leetcode Split Array Largest Sum
- 875. Koko Eating Bananas
- Leetcode : Minimum Number of Days to Make m Bouquets
Recursion
Introduction
⭐ Think of it as solving smaller problems to eventually solve a big problem. So if you want to climb
Mount Everest, you can recursively climb the smaller parts until you reach the top.
Another example is that you want to eat ‘15 butter naan’, so eating all of them at once won’t be
feasible. Instead, you would break down those into 1 at a time, and then enjoy it on the way.
2
30DaysCoding.com
Solving a lot of recursive problems will help you understand 3 core concepts
- Recursion
- Backtracking
- Dynamic programming
Watch this amazing video: Recursion for Beginners: A Beginner's Guide to Recursion
⭐ These are some questions I have when I look at a recursive question/solution, you probably have the
same. Let’s try to figure out them
- What happens when the function is called in the middle of the whole recursive function?
- What happens to the stuff below it?
- What do we think of the base case?
- How do we figure out when to return ?
- How do we save the value, specially in the true/false questions?
- How does backtracking come into place, wrt recursion?
Let’s try to answer these one by one. A recursive function means that we’re breaking the problem
down into a smaller one. So if we’re saying function(x/2) -> we’re basically calling the function again
with the same parameters.
2
30DaysCoding.com
So if there’s something below the recursive function -> that works with the same parameter. For
instance, calling function(x/2) with x=10 and then printing (x) after that would print 10 and then 5 and so
on. Think of it as going back to the top of the function, but with different parameters.
The return statements are tricky with recursive functions. You can study about those things, but
practice will help you get over it. For instance, you have fibonacci, where we want to return the sum of
the last 2 elements for the current element -> the code is something like fib(n) + fib(n-1) where fib() is
the recursive function. So this is solving the smaller problem until when? -> Until the base case. And the
base case will return 1 -> because eventually we want the fib(n) to return a number. This is a basic
example, but it helps you gain some insights on the recursive part of it.
Something complex like dfs or something doesn’t really return anything but transforms the 2d matrix or
the graph.
Backtracking is nothing but exploring all the possible cases by falling back or backtracking and going to
other paths.
2
30DaysCoding.com
The first condition is if there are more than 0 open / left brackets, we recurse with the right ones. And if
we have more than 0 right brackets, we recurse with the left ones. Left and right are initialized at N - the
number given.
if(left>0){
There’s parentheses(list,
a catch. We can’t add the “)” everytime
s+"(", right, weleft-1);
have right>0 cause then it will not be balanced. We
} balance that with a simple condition of left<right.
can
if(right>0){
Base case? When both right and left are 0? -> cause we’re subtracting one as we go down to 0. Here’s
parentheses(list, s+")", right-1, left);
the
} final thing:
if(left>0){
dfs(list, s+"(", right, left-1);
}
if(left<right && right>0){
dfs(list, s+")", right-1, left);
}
2
30DaysCoding.com
def reverseList(self,
Here’s ahead):
nice video with=the
prev explanation: Reverse a Linked List Recursively
None
We canwhile head:
also solve this recursively and it’s a great way to understand it in a better way. Here’s how we
curr = head
do it:
head =
- Storehead.next
the recursive call in a node -> This takes the pointer to the end
- Pointcurr.next =
the curr’s next pointer to that
prev prev =
- Point head’s next to null -> this will be the tail (at every instance)
2
30DaysCoding.com
These are problems and patterns where we see a bigger number and we want to break it down into a
smaller thing to test. This is in alignment with the core of recursion, but it’s easier to understand when
math comes into play.
Let’s discuss a question, Power of 3. We want to return true if the number given is a power of 3.
- Iterate and find the powers -> match them
- Optimized: Iterate for less numbers
- Recursively try to solve smaller problems and break it down into n/3 every time (power of 3)
bool isPowerOfThree(int n)
{
if(n<=0)return
Another question, to solidify the concept: Power of 2.
false; if(n%3==0)
Super similar to power of 3, let’s look at possible solutions and maybe a new approach for this.
return isPowerOfThree(n/3);
- if(n==1)return
Iterate and find powers, match if possible
- true; return
Optimized: Iterate for less numbers and then match
false;
- Recursively break it down into n/2 if it doesn’t match and have base cases to check
def isPowerOfTwo(self, n:
int): if n==0:
return False
if n==1:
return True
if n%2!=0:
return False
return isPowerOfTwo(n//2)
3
30DaysCoding.com
combo(combination+letter, digits[1:])
We do this for every letter and add a base case for adding the combination to the result array. Here’s
how the complete code looks like
def combo(combination,
Here’s a java solution code
digits): if for it: My recursive solution using Java
len(digits)==0:
Let’s also look at an iterative way of solving this. We can simply take a Queue and use BFS (sort of)
a.append(combination)
to iterate and then add the letters when the conditions are true. We can iterate over the digits, add
the possible combinations if the size is valid.
else:
for letter in phone[digits[0]]:
Here’s a nice solution for it: My iterative solution, very simple under 15 lines.
Backtracking goes hand in hand with recursion and we’ve discussed many more questions and patterns
in that section, so definitely follow that after this.
3
30DaysCoding.com
Read ‡ ,)<›<‹ˇ‘
- Reading 10: Recursion
- Recursion for Coding Interviews: The Ultimate Guide
Videos ]-·
- Fibonacci Sequence - Recursion with memoization
- Introduction to Recursion (Data Structures & Algorithms #6)
- Intro to Recursion: Anatomy of a Recursive Solution
Questions ӳ◆
- Explore: Leetcode Part I
- Explore: Leetcode Part II
- 150 Questions: Data structures
Extra
- Complex Recursion Explained Simply
- Recursion Concepts every programmer should know
Backtracking
Introduction
⭐ Backtracking can be seen as an optimized way to brute force. Brute force approaches evaluate every
possibility. In backtracking you stop evaluating a possibility as soon as it breaks some constraint
provided in the problem, take a step back and keep trying other possible cases, see if those lead to a
valid solution.
Think of backtracking as exploring all options out there, for the solution. You visit a place,
there’s nothing after that, so you just come back and visit other places. Here’s a nice way to
think of any problem:
- Recognize the pattern
3
30DaysCoding.com
Problem 1: Permutations
46. Permutations
⭐ We have an array [1,2,3] and we want to print all the possible permutations of this array. The initial
reaction to this is - explore all possible ways -> somehow write 2,1,3, 3,1,2 and other permutations.
Second step, we recognize that there’s a pattern here. We can start from the left - add the first element,
and then explore all the other things with the rest of the items. So we choose 1 -> then add 2,3 and 3,2 -
> making it [1,2,3] and [1,3,2]. We follow the same pattern with
others. How do we convert this into code?
- Base case
- Create a temporary list
- Iterate over the original list
- Add an item + mark them visited
- Call the recursive function
- Remove the item + mark them univisited
Great article on more backtracking problems templates: A general approach to backtracking questions
in Java (Subsets, Permutations, Combination Sum, Palindrome Partitioning)
if(curr.size()==nums.length){
res.add(new ArrayList(curr));
return;
}
for(int i=0;i<nums.length;i++){
if(visited[i]==true)
continue; curr.add(nums[i]);
visited[i] = true;
backtrack(res,nums,
curr,visited);
curr.remove(curr.size()-1);
visited[i] = false;
3
30DaysCoding.com
There are also other solutions to problems like this one, where you can modify the recursive function to
pass in something else. We can pass in something like this: function(array[1:]) -> to shorten the array
every time and then have the base case as len(arr) == 0.
Problem 2: Subsets
https://leetcode.com/problems/subsets/
⭐ We want all the possible subsets of an array [1,2,3]. Super similar to the permutations question, but
we don’t want to make the array shorter or anything, Just explore all the possible options.
We usually make a second function which is recursive in nature and call that from the first one -> it’s
easier, cleaner, and more understandable. There are certain ways of doing it in the same function, but
this is better.
Let’s build the backtrack function. Let’s use our template logic:
- Iterate over the array
- Add the item
- Backtrack - recursive call
- Remove the item
And then think of the base
case...
3
30DaysCoding.com
tempList.remove(tempList.size() - 1);
}
}
Base case?
We want all the possible cases -> just simply add to a new list that we pass in?
list.add(new ArrayList<>(tempList));
Something to note here is that we add a new copy of the array (templist) -> and not the same templist
because of recursion. Try it!
⭐ There are other solutions to problems like these and backtracking problems in general. You can
avoid the for loop and iterate over the array through the index you pass in to the function. Here are
some things to consider while considering this approach
So this is more on the lines of brute force when you have a CHOICE. A general approach there is to
recurse when you’ve chosen the item and when you’ve not chosen it.
// with array[index]
path.push(array[index]); // add
array[index] recur(acc, ns, path, index +
1);
path.pop(); // remove array[index]
// without array[index]
3
30DaysCoding.com
Read this carefully before moving forward. It’s important to make the right CHOICES in your life haha.
Make sure they’re the good ones. Read more here: A general approach to backtracking questions in Java
(Subsets, Permutations, Combination Sum, Palindrome Partitioning)
3
30DaysCoding.com
Give this a good read, watch this: AMAZON CODING INTERVIEW QUESTION - COMBINATION SUM II
(LeetCode) and make sure to understand it before moving forward.
Problem 4: N-queens
51. N-Queens
⭐ We want to place 8 queens such that no queen is interacting with each other. We see a similar
pattern, where the thinking goes like this -> we want to explore all possible ways such that eventually
we find an optimal thing, where queens don’t interact with each other.
We start by placing the first, then second… until there’s a conflict. We then would have to come back,
change the previous queens, until we find the optimal way. We would have to go back to the very start
as well, and maybe try the whole problem again.
How to convert this into code?
Similar to most backtracking problems, we will follow a similar pattern:
- Place the queen on a position
- Check if that position is valid === Call the recursive function with this new position
- Remove the queen from that position
3
30DaysCoding.com
Make sure to think about the base cases, recursive calls, the different parameters, and validating
functions. Reference: Printing all solutions in N-Queen Problem
Memoization
⭐ Memoization means storing a repetitive value, so that we can use it for later. A really nice example
here:
- If you want to climb Mount Everest, you can recursively climb the smaller parts until you reach
the top. The base case would be the top, and you would have a recursive function climb()
which does the job.
- Imagine if there are 4 camps to Mount Everest, your recursive function would make you climb
the first one, then both 1 and 2, then 1-2-3 and so on. This would be tiring, cost more, and a
lot of unnecessary work. Why would you repeat the work you’ve already done? This is where
memoization comes in.
- If you use memoization, you would store your camp ground once you reach it, so the next time
your recursive function works, it’ll get the camp ground value from the stored set.
function(i, value,
something...){ if
base_case:
do something
if stored_value[i]:
return stored_value[i]
3
30DaysCoding.com
stored_value[i] = value
}
Dynamic programming is Backtracking + Memoization. That’s it. Every problem is a part of this
algorithm -> explore all possible ways and then optimize them in such a way that we don’t explore
already explored paths. Stop solving dynamic programming problems the iterative way. Practice tons of
recursion + backtracking problems, and then go the iterative way.
Read ›‡‘ˇ<),‹
- A deep study and analysis of Recursive approach and Dynamic Programming by solving the
most…
- Leetcode Pattern 3 | Backtracking | by csgator | Leetcode Patterns
- A general approach to backtracking questions in Java (Subsets, Permutations, Combination Sum,
Palindrome Partitioning)
- WTF is Memoization. Okay, those who saw this term for the… | by Leo Wu | Medium
Questions ◆ӳ◆
- Word Search
- Leetcode #78 Subsets
- 90. Subsets II
- Letter Case Permutation
- 17. Letter Combinations of a Phone Number
- Combinations
- 39. Combination Sum
- Leetcode : Combination Sum II
- 216. Combination Sum III
- Combination Sum IV
- 46. Permutations
- 47. Permutations II
- 31. Next Permutation
- 51. N-Queens
3
30DaysCoding.com
BFS, DFS
Introduction
⭐ These are searching techniques to find something. It’s valid everywhere: arrays, graphs, trees, etc. A
lot of people try to confuse this with being something related to graphs, but no -> this is just a technique
to solve a generic search problem.
Here’s a great visualizer tool: Graph Traversal (Depth/Breadth First Search)
Try to understand the iterative way of solving a DFS or BFS question and how things work. There are 3
basic things
- Push the first node
- Iterate over all nodes (first time it’s just the root)
- Pop the top element
- Add the neighbors
4
30DaysCoding.com
Here’s a beautiful visualization of a search in a tree: Branch and Bound - Depth-Limited Search
Here’s a general iterative dfs pseudo-code template:
def dfs(root,
The second step is that
target): of MEMOIZATION
stack = and we want to keep a track of all the nodes visited when
[]
we’re iterating over. Here’s a complete version of a BFS algorithm where we keep track of the visited
stack.append(root) # add the first item
node using an array discovered []
This could be anything
while - array, map, set - depending on the situation. The only thing we need is to store
len(stack)>0:
nodeso=that
the visited things stack.pop() # pop any
we’re not repeating thework.
grid item
if(node ==
target):
public static void BFS(Graph graph, int v, boolean[] discovered)
return true
{
// create a queue for doing BFS
# explore more
Queue<Integer> q = new ArrayDeque<>();
# For trees -> if root.left or root.right
if (condition):
// mark the source vertex as discovered
stack.append(new_item)
discovered[v] = true;
4
30DaysCoding.com
Trying to think of a recursive way to do this is also very important. We call dfs for every node after
exploring the neighbors and can do that in a couple of ways -> inside the for loop or outside the for
loop after adding the neighbors to a list. Here’s an approach, also linking other approaches below.
4
30DaysCoding.com
recursiveBFS(graph, q, discovered);
}
Here are some implementations and use cases for DFS, BFS:
DFS:
- Find connected components in a graph
- Calculate the vertex or edges in a graph
- Whether the graph is strongly connected or not
- Wherever you want to explore everything or maybe go in depth
BFS
4
30DaysCoding.com
Input: grid = [
We want to connect all the 1’s together, so that we form an island and then count those islands. A
["1","1","0","0","0"
human way to do this is just count the connected 1’s and then keep a track of those. How do we code it?
],
⭐ The core principle of DFS kicks in -> we start from the 1st node, pop it, mark it visited, explore all the
["1","1","0","0","0"],
neighbors, and then repeat. Once this exploration is done, we start with another 1, explore all of it’s
["0","0","1","0","0"],
connected 1’s and then mark those visited.
["0","0","0","1","1"]
Every
] time we explore a new node island, we increase the count by 1 and eventually return that
number. Sounds easy? Go code it first… I’m waiting.
Glad you’re back, let’s solve this both iteratively and recursively.
4
30DaysCoding.com
4
30DaysCoding.com
The idea is the same, we start from the 1s, explore all the connected components, mark them visited,
and then increase the count for every 1. These are the steps:
- Iterate over the find the 1’s
- Call dfs for every 1 found
Iterative DFS
- Push the grid node for the 1 to the stack
- Mark the node visited (change it to something else)
- Pop the node, explore all the 4 valid neighbors
- Add those neighbor nodes to the stack
count=0
for i in range(len(grid)):
for j in
range(len(grid[0])): if
grid[i][j]=='1':
dfs(grid, i,
j) count+=1
return count
def dfs(grid, i,
j): s=[]
s.append((i,j))
while len(s)>0:
a,b =
s.pop()
grid[a]
[b]='X'
if a>0 and grid[a-1][b]=='1':
s.append((a-1,b))
if b>0 and grid[a][b-1]=='1':
s.append((a,b-1))
if a<len(grid)-1 and grid[a+1]
[b]=='1': s.append((a+1,b))
4
30DaysCoding.com
Pattern: 2D Matrix
⭐ There are tons of problems where there’s something to find or connect in a 2d array where the
confusions just increase. This approach will help you connect the dots and approach those problems
with DFS or BFS iteratively on that array.
There’s nothing special here, but it’s good to notice how we can take [0,0] as the root and basically
convert this into a 2d matrix. This is a general way of adding the point to the queue in java, with the
help of an additional class Pair => q.offer(new Pair(i, j));
4
30DaysCoding.com
Here’s how I would do it -> Think of adding the root, take out the root, add the whole second layer or
basically all the children of the previous layer’s nodes. The catch here is to add the whole level at once.
We can do that by getting the size of the queue and then iterating over it every time.
At the end, the queue would have the next level and we’ll repeat the whole process again for the next
nodes. Here’s how the code looks:
queue.add(root);
while(!q.isEmpty()){
List<Integer> list = new
ArrayList<>(); int children =
queue.size();
// iterate over all the
children for(int i=0 ;
i<children; i++){
TreeNode node = queue.remove();
if(node.left!=null)
queue.add(m.left);
if(node.right!=null)
queue.add(m.right);
4
30DaysCoding.com
return solution;
First step is to prepare the queue. We add the rotten oranges (represented by 2) to the queue and also
count the total number of oranges. 0 -> means an empty place.
4
30DaysCoding.com
while (! q.isEmpty())
{ int size =
q.size(); rotten
+= size;
// if the total number of rotten oranges matches our local variable
// then return the time it took
if (rotten == total_rotten) return time;
time++;
// something
queue.offer(new int[]{x ,
y}); rotten_oranges++;
}
5
30DaysCoding.com
Read ,)<›‹<‘‡ˇ
- Leetcode patterns 1
- Leetcode Patterns 2
- Depth-First Search (DFS) vs Breadth-First Search (BFS) – Techie Delight
Videos ]-·
- Breadth First Search Algorithm | Shortest Path | Graph Theory
- Depth First Search Algorithm | Graph Theory
- Breadth First Search grid shortest path | Graph Theory
Questions ◆ӳ◆
- Flood Fill
- Leetcode - Binary Tree Preorder Traversal
- Number of Islands
- Walls and Gates
- Max Area of Island
- Number of Provinces
- 279. Perfect Squares
- Course Schedule
- C/C++ Program for Detect cycle in an undirected graph
- 127. Word Ladder
- 542. 01 Matrix
- Rotting Oranges
- 279. Perfect Squares
- 797. All Paths From Source to Target
- 1254. Number of Closed Islands
5
30DaysCoding.com
Dynamic Programming
Introduction
⭐ Dynamic programming is nothing but recursion + memoization. If someone tells you anything outside
of this, share this resource with them. The only way to get good at dynamic programming is to be good
at recursion first. You definitely need to understand the magic of recursion and memoization before
jumping to dynamic programming.
The day when you solve a new question alone, using the core concepts of dynamic programming ->
you’ll be much more confident after that.
So if you’ve skipped the recursion, backtracking, and memoization section -> go back and complete
those first! If you’ve completed it, keep reading. You will only get better at dynamic programming (and
problem solving in general) by solving more recursion (logical) problems.
Problem 1: 01 Knapsack
⭐ This is the core definition of dynamic programming. Understanding this problem is super important,
so pay good attention. Every problem in general, and all DP questions have a CHOICE at every step.
We have a weights array and a values array, where we want to choose those values which will return us
the maximum weight sum (within the limit). There is a max weight given, which we have to take care
of. Just from the first glance, I see that maxWeight will help us with the base case. At every step, we
have 2 CHOICES:
5
30DaysCoding.com
Thinking about the arguments, a good recursive function would be passing in the weights, values, index,
and the remaining weight? That way remaining_weight == 0 can be our base case. You can absolutely
have other recursive functions with different arguments, it’s about making things easier.
5
30DaysCoding.com
There is a problem here, we’re doing a lot of repetitive work here, do you see it? No? Go wash your
eyes.
We’re re-calculating a lot of states -> where the value maxWeight - weights[i] value is something. For
example 5 is 8-3 but it’s also 9-4. So we don’t want to do this, how can we stop this? MEMOIZATION
Simply store the max value and return it with the base case. You can think of memoization as your
SECOND base case.
5
30DaysCoding.com
memo_set.put(key,Math.max(op1, op2));
return Math.max(include, exclude);
We set the key and max value in the set, and then use that in the base case to return when that
condition is reached. This is the recursive approach and once you’ve understood how the basis of this
works, you can go to the iterative version. It’s very important to solve and understand it recursively
before moving forward.
- Watch this awesome visualization to understand it more: Dynamic Programming - Knapsack Problem
- Read more here: 0–1 Knapsack Problem – Techie Delight
A human way to look at this is to make quick decisions and see where the biggest numbers are, and then
choose them. However, humans would fail if this grid is really big.
How do we solve this using a program?
At every step, we make a CHOICE. Either we go down or we go right. And this is where recursive kicks
in, making this a dynamic programming question -> where we try to solve small problems to eventually
solve the big one.
5
30DaysCoding.com
current_sum = grid[row]
Now we can make it even easier by just passing the rows and columns instead of the whole grid and
[col] max_sum = min(
bringing out some things to clean it.
current_sum + function(grid[row+1][col]),
current_sum + function(grid[row][col+1]
))
current_sum = grid[row][col]
Instead of calculating the sum at every step, we pass it back to the recursive function who does the
max_sum = current_sum + min(function(row+1, col),function(row, col+1]))
magic for us. We would have a BASE CASE which helps us in solving the smaller problem, which
eventually solves the big one.
/** When we reached the first row, we could only move horizontally.*/
if(row == 0) return grid[row][col] + min(grid, row, col - 1);
/** When we reached the first column, we could only move vertically.*/
if(col == 0) return grid[row][col] + min(grid, row - 1, col);
5
30DaysCoding.com
Here’s the deal: We have costs for 1, 7, and 30 days, and an array of the days we’re travelling, we want
to optimize it such that the cost is the lowest. Solving it a humanly way, we would check all the possible
ways and then make a decision -> hence making it a DP problem -> we explore all the possible cases
with brute force and then memoize it.
Exploring all the cases:
5
30DaysCoding.com
This can be different depending on your conditions -> maybe you’re iterating over the days array
through a for loop and creating some magic there. A right base case would probably be validating the
current day or something in that case.
private static int rec(int days[], int costs[], int i, int dp[]){
if(i >= days.length) return 0;
Problem 4: Buy and sell stocks 3
int option_1day = costs[0] + rec(days, costs, i+1, dp);
Leetcode - Best Time to Buy and Sell Stock III
int
⭐ Try this k Q.
first: = 121.
i; Best Time to Buy and Sell Stock, although they’re not similar, but it’s nice to get
for(; k <days.length; k++){
a feel of that one before coming to this one.
if(days[k] >= days[i] + 7){
Let’s discuss this one. We have an array, we have to buy and then sell - 2 times, and then find the
break;
maximum profit } we can earn by doing this. Eg [3, 3, 5, 0, 0, 3, 1, 4], let’s solve this in a human way.
}
int option_7days = costs[1] + rec(days, costs, k, dp);
5
30DaysCoding.com
Buy at 3, sell at 5. Then buy at 0, sell at 4. Total is 6. Easy? I just thought of the difference as I was going,
what could be the maximum difference. However, this approach can only work for very simple
examples or the first question (Q. 121. Best Time to Buy and Sell Stock).
Let’s solve it through code.
At every step when we iterate from left to right, we have a CHOICE. It’s a little complex, think a little.
The CHOICE is to either buy or not buy OR sell or not sell when you’re at that step. We do this because
we’re buying or selling only 2 times. Here’s how the choices look:
Problem 5: Paint
// if we're houseright
selling now
lets_sell
leetcode = function()
256. Paint + array[i]
House (Python)
⭐ There are a row of n
lets_not_sell = houses, each house can be painted with one of the three colors: red, blue or
function()
green. The cost of painting each house with a certain color is different. You have to paint all the houses
such that no two adjacent houses have the same color.
At every step, we have a CHOICE to choose a color and then see what would be the maximum at the
very end. So we explore all the possible cases, remove the repetitive cases using memoization, and
eventually solve the question by ‘DP’. At every step,
- If you choose red, then choose the min of blue or green from previous row
- If you choose blue, then choose the min of red or green from previous row
- If you choose green, then choose the min of red or blue from previous row
5
30DaysCoding.com
Just kidding, let’s make it easy. 3 choices? 3 recursive options -> insert, delete, and update. But there’s a
catch, deletion doesn’t mean we’re deleting -> we’ll just call the string[1:] or string.substring(1) in the
recursive function to create the deletion identity. Same for inserting -> adding a letter in one string,
means deleting something from the other (in a way), so we can mix and match the deleting/insertion
operations. Coming to update -> that just means we’re changing that letter and moving forward, so the
recursive call will be first[1:] and second[1:]. Here’s how the recursive calls look like:
6
30DaysCoding.com
Base case? You forgot right? Well, forget getting that internship then. Just kidding, let’s think of the base
case -> if we have both the strings inside our function -> if one of them finishes (because we’re taking
substrings) -> we should handle those cases. Here’s how that will look:
if(first_string.length() == 0)
return second_string.length();
if(second_string.length() ==
0)
Matching case? We also want to recurse with substring(1:) when both the characters match. This is the
same as the update operation but without adding 1 to the final result.
Watch this awesome visualization: Dynamic Programming - Levenshtein's Edit Distance
Here’s the combined result:
if(first.length() == 0)
return second.length();
6
30DaysCoding.com
Read ‘ ,)<›<‹ˇ‡
- My experience and notes for learning DP
- Dynamic Programming (Theory - MIT)
- Dynamic Programming (Theory MIT)
Videos -]·
- MIT Playlist: 19. Dynamic Programming I: Fibonacci, Shortest Paths
- Dynamic Programming - Learn to Solve Algorithmic Problems & Coding Challenges
Questions ◆ӳ
Easy
- 53. Maximum Subarray
- 509. Fibonacci Number
- 70. Climbing Stairs
- Min Cost Climbing Stairs
- N-th Tribonacci Number
Medium
- 322. Coin Change
- 931. Minimum Falling Path Sum
- Minimum Cost For Tickets
- 650. 2 Keys Keyboard
- Leetcode #152 Maximum Product Subarray
- Triangle
- 474. Ones and Zeroes
- Longest Arithmetic Subsequence
- 416 Partition Equal Subset Sum
- 198. House Robber
- Leetcode - Decode Ways
6
30DaysCoding.com
Trees
Introduction
⭐ I love trees, but actual ones - not these. Just kidding, I love all data structures. Let’s discuss trees.
They’re tree-like structures (wow) where we can store different things, for different reasons, and then
use them to our advantage. Here’s a nice depiction of how the actually look:
Recursion is a great way to solve a lot of tree problems, but the iterative ones actually bring out the
beauty of them. Making a stack and queue, adding and popping things from that, exploring children,
and repeating this would definitely make sure you understand it completely. You should be seeing this
visually in your head, when you do it iteratively.
6
30DaysCoding.com
Pattern: Traversals
⭐ There are 3 major ways to traverse a tree and some other weird ones: let’s discuss them all. The most
famous ones are pre, in, and post - order traversals. Remember, in traversals -> it’s not the left or right
node (but the subtree as a whole).
Inorder traversal
Let’s start with inorder traversal: We define a stack and will traverse the tree iteratively. Recursive
solutions to these 3 basic ones are pretty straightforward, so we’ll try to understand them a little more
with iterative ones.
We start with the root, move until it’s null or the stack is empty. We move to the left if we can, if not ->
we pop, add the popped value and then move right.
6
30DaysCoding.com
result = [];
while (!stack.empty())
{
Node curr = stack.pop();
result.push(curr.data);
// print node
if (curr.right != null) {
stack.push(curr.right);
}
if (curr.left != null) {
stack.push(curr.left);
}
}
while (!stack.empty())
{
Node curr = stack.pop();
result.push(curr.data);
if (curr.left != null) {
stack.push(curr.left);
}
if (curr.right != null) {
stack.push(curr.right);
}
}
// Print the REVERSE of the result.
6
30DaysCoding.com
// Or store it in a stack
Additional questions
- LeetCode 102 - Binary Tree Level Order Traversal [medium]
- Kth Smallest Element in a BST
- Leetcode #98 Validate Binary Search Tree
- Binary Tree Zigzag Level Order Traversal
- Binary Tree Right Side View
Applications
- Number of nodes
- Height of tree or subtree
- Heap sorting
while (!queue.isEmpty())
{ minimumTreeDepth++;
int levelSize = queue.size();
for (int i = 0; i < levelSize; i++) {
TreeNode currentNode = queue.poll();
6
30DaysCoding.com
This is the same as level order tree traversal -> we push the node initially, pop it -> add the children on
the next level and then repeat the process.
Having a parent node relation is important here, so here’s the first thing I think: We make a map and
store {parent: node} inside that map as we go down.
6
30DaysCoding.com
So we do a simple iterative DFS, store the parent node relation, and then come back to see that relation
to find the common node.
Here we fill in the parent_map and try to build the parent node relation for all nodes. Once we have the
map ready, we can use that map to go back and find the common ancestor. We make a set, add the
first node in the set while iterating over the parent_map, then we check for the node_2 in the set and
when we find that -> we break the while loop and return node_2 at that point.
node_set = set()
It’swhile
often hard to come up with recursive solutions instantly, but over time - you’ll be more comfortable
node_1:
(I’m notnode_set.add(node_1)
:P) to bring them up.
node_1 =
More solutions here: Lowest Common Ancestor of a Binary Tree
parent_map[p]
while node_2 not in node_set:
Problem node_2 = parent_map[node_2]
3: Binary tree to BST
⭐ We have a binary tree and we want to convert that into a binary search tree, where the left subtree is
smaller than the root, and the right subtree is greater than the root.
Doesn’t this look similar to the inorder traversal ??? Inorder traversal gives us the binary search tree in a
sorted order, so we can use that to bring it back up as well.
Wait… whaaaat? Haha yeah.
6
30DaysCoding.com
We just need an iterator to traverse through the next nodes from an array, list, set, or something else.
Here’s how it’ll look:
Questions ◆ӳ
- Leetcode - Binary Tree Preorder Traversal
- Leetcode #94 Binary Tree Inorder Traversal
- Leetcode - Binary Tree Postorder Traversal
- Leetcode #98 Validate Binary Search Tree
- 783. Minimum Distance Between BST Nodes
- Symmetric Tree
6
30DaysCoding.com
- Same Tree
- Leetcode #112 Path Sum
- Leetcode #104 Maximum Depth of Binary Tree
- Leetcode #108 Convert Sorted Array to Binary Search Tree
- Leetcode #98 Validate Binary Search Tree
- Binary Search Tree Iterator
- 96. Unique Binary Search Trees
- Serialize and Deserialize BST
- Binary Tree Right Side View
- 96. Unique Binary Search Trees
- Binary Search Tree Iterator
Graphs
Introduction
⭐ A lot of graph problems are covered by DFS, BFS, topo sort in general -> but we’re going to do a
general overview of everything related to graphs. There are other algorithms like Djikstra’s, MST, and
others - which are covered in the greedy algorithms section.
A lot of graph problems are synced with other types = dynamic programming, trees, DFS, BFS, topo sort,
and much more. You can think of those topics sort of coming under the umbrella of graph theory
sometimes.
7
30DaysCoding.com
⭐ A human way of finding the root will be to look at 4 and say that there are no incoming edges at 4, so
it’s the root. Think of it in a tree like format, where the root is at the top and we have children below it.
An important part of graph algorithms is also to transform the given input into an adjacency list. We
iterate over the edges and make a mapping from source to destination, something like this:
7
30DaysCoding.com
So the solution here seems to be trivial -> we iterate over, find the new nodes, mark the neighbors
visited, and then finally return the vertex. DFS/BFS anything works -> let’s try to do it recursively. We
have the theory of strongly connected components here, which is used to find different sets of nodes in
a graph which are connected with each other -> which can be modified to return a node with no vertex.
Let’s analyse through code. We explore and call dfs on the nodes and keep a track of the last node:
7
30DaysCoding.com
- Why are we returning -1 => because we didn’t find the vertex if there are more than 1 nodes not
explored -> meaning more than 1 vertex.
We want to color the nodes in a way that no 2 consecutive nodes have the same color. There are a lot of
implementations attached to this concept, some of them are:
- Scheduling: Problems where we have a list of jobs or times or rooms, and we want to find
an optimal way to find the schedules of different things.
7
30DaysCoding.com
- Other seating related problems can also be solved using this approach - where we don’t want
any 2 people sitting next to each other.
7
30DaysCoding.com
Credits
- Graph coloring - Wikipedia
- Scheduling (computing)
boolean visited[]=new
Soboolean[V];
we need an additional
for(intcondition here: To check for the parent node when writing Dfs code. So how
i=0;i<V;i+
do+){
we check for the parent?
if(!visited[i]){
if(DFS(adj,i,visited,-1))
return true;
}
}
7
30DaysCoding.com
We can pass in the parent value every time we’re calling the recursive function. So every time you
explore a new node, you pass in that node as the ‘parent’ variable. The next time that parent variable
gets changed to the new node.
7
30DaysCoding.com
stack.append(neighbor)
⭐ There’s a second part to this, where we find a cycle in a directed graph. Here’s a nice visualizer to
see that in action: Simple Recursive - Cycle Detection
7
30DaysCoding.co
Here’s the catch -> we want to call dfs() for every node in the list that we have, so either we can make a
separate function or just do it inside the loop. Here’s how the dfs would look:
while(!dfs.empty()){
int current = dfs.top(); dfs.pop();
visited[current] = true;
We iterate over the list of nodes and call this for every node -> marking all the connected components
visited and increasing the counter once for every new node. Here’s how the complete code looks like.
7
30DaysCoding.co
⭐ Understand the underlying principles, visualize it in your head, and explain it to your mind before
moving forward. A lot of times, you won’t have to code the whole thing in an interview, but explain,
explain, explain! They want to know your approach and understanding before knowing how you write
code.
Algorithms
⭐ There are other, advanced graph algorithms which are good to know and often overlap with some
shortest path questions. So here are some links you can refer to, when studying about these:
● Kruskal's Algorithm
● Detect Cycle In Graph
● Union Find Algorithm In Graph
● Prim's Algorithm
⭐ Missing something? Email us at 30dayscoding@gmail.com and let us know if you need additional
help! We’re happy to help you with more resources.
Read ‘)‡<ˇ‹›<,
- A Gentle Introduction To Graph Theory | by Vaidehi Joshi | basecs
- Advanced Graph Algorithms: Dijkstra's and Prim's | by Mikyla Zhang | Medium
- 10 Graph Algorithms Visually Explained | by Vijini Mallawaarachchi
Videos ]·-
- Intro: Graph Theory Introduction
- Intro: Lecture 6: Graph Theory and Coloring | Video Lectures | Mathematics for
Computer Science | Electrical Engineering and Computer Science
- Intro: Lecture 12: Graphs and Networks | Video Lectures | Computational Science
and Engineering I | Mathematics
- Dijkstra's Shortest Path Algorithm | Graph Theory
7
30DaysCoding.co
Questions ◆ӳ ❓
- Employee Importance
- Redundant Connection
- 130 Surrounded Regions
- 721. Accounts Merge
- Leetcode-Clone Graph
- Word Search
- Network Delay Time
- Is Graph Bipartite?
- 802. Find Eventual Safe States
- 841. Keys and Rooms
- Leetcode : Possible Bipartition
- [947] Most Stones Removed with Same Row or Column
- 994. Rotting Oranges
- 787. Cheapest Flights Within K Stops
- 1319. Number of Operations to Make Network Connected
⭐ Here are some famous topics and algorithms under graph theory, which are exciting to know about
but aren’t necessarily used directly in coding interviews:
- Prim’s algorithm
- Kosaraju’s algorithm
- Bellman ford
- Floyd Warshall
There are also other algorithms which are discussed in the section below here.
Topological Sorting
80
30DaysCoding.co
Introduction
⭐ The name suggests sorting, so it probably should be :P. Here’s the definition: “topological ordering
of a directed graph is a linear ordering of its vertices such that for every directed edge uv from vertex
u to vertex v, u comes before v in the ordering”
In simple words, we need to sort then in such a way that that the ‘prerequisite’ comes before all the
others and we have a directed structure from one node to another.
Let’s understand this with CLASSES at your school/college/university. You have to take calculus before
taking advanced mathematics and you have to take basic programming before moving forward -> that’s
topological sorting. You can make your class schedule using this algorithm.
Here’s a beautiful way to see topological sorting in action: Branch and Bound - Topological Sort
81
30DaysCoding.co
Removing edge means marking it visited and never coming back to it again. So here’s what the first thing
looks like.
82
30DaysCoding.co
After filling up the stack, we print out all the items from that in a sorted form. Here’s a nice explanation
video by WIlliam Fiset on topo sort: Topological Sort Algorithm | Graph Theory
Here’s another version of the code for topological sorting: Python Topological Sorting, [Topological Sort
Algorithm]
Now that we know the basics of topological sorting, let’s understand it more through a question -> the
most popular one: course schedule.
Onto the question -> We have the number of courses and an array of prerequisites -> the prerequisites
can be multiple for some classes. Like you might have to take CS101 for 120 as well as 130. So we need
to take that into consideration as well.
Firstly, let’s transform the prerequisites so that we can use them. We transform the 2D matrix into a
graph-like thing where we have a key -> value thing for prerequisite -> course.
83
30DaysCoding.co
We want to call DFS on all the nodes as we go and mark them visited once we cover them -> basics of
topological sorting.
Onto writing the dfs() function where we visit every node from that one node, mark the neighbors
visited and keep a track of the eventual course structure -> whether we can take the courses or not.
The return type is a little different as we’re returning true or false based on that particular node. So we
visited the courses, store them in a visited array and return true if we’re able to take the courses from
there. We do this for all other nodes until we find a negative result. If we don’t, we return true at the
end.
84
30DaysCoding.co
Read ˇ ,)<›<‹‘‡
- Definition: Wikipedia
- Visualizer: Branch and Bound - Topological Sort
Videos - ·]
- Topological Sort Algorithm | Graph Theory
- Topological Sort | Kahn's Algorithm | Graph Theory
Questions ❓
- Topological Sort
- Leetcode : Find the Town Judge
- LeetCode 210. Course Schedule II
Greedy Algorithms
Introduction
⭐ Algorithms where we make choices at every step because of a reason (optimal choice) are called
greedy algorithms. Like returning the max everytime in an array, or maybe returning the cheapest food
near you from a list of restaurants with multiple menu items. Greedy answers can definitely work, but it
might not be the most optimal thing to do wrt time and space complexity.
For instance, you have a tree and you want to find the maximum path sum of that tree. The correct
solution to that would be to explore all different cases, add memoization to the logic, and finally return
85
30DaysCoding.co
the max path sum from that. However, if you try to use a greedy approach right from the top, you
would end up making the wrong mistake of choosing the maximum element at every level - which
would be wrong. So we have to be smart about using it at the right time. Here are some sub topics
which will help you understand things in a better way.
A lot of questions can be solved by sorting the input and then adding some logic to that. Let’s discuss a
question: meeting rooms. We have the starting and ending times for a room throughout the day. And
we want to check how many people can be there at the maximum time or something -> so we sort the
times, arrange the people in terms of the time and then find the maximum while iterating through the
instances. Let’s discuss some problems on similar concepts.
Brute force looks annoying here, we iterate over, find all possible cases, memoize something, and then
finally return the optimal answer. We want the minimum rooms, so we condition something on that,
and return that.
What if we change the game a little here, what if we track every time someone comes in and goes (after
sorting). So if we sort this array -> we would see someone comes at 2,3,5 and someone leaves at 4,7,7.
86
30DaysCoding.co
What if -> we turn the people anonymous and every time someone comes in, we +1 the counter, and
everytime someone leaves the room, we -1 the counter? Try it. While doing this, we store the max value
of the counter and eventually return that max value. That’s the maximum number of rooms we need.
87
30DaysCoding.co
// If, after being sorted, the largest number is `0`, the entire number
// is zero.
if (asStrs[0].equals("0")) {
return "0";
}
return largestNumberStr;
Priority Queue
⭐ Priority Queue is a big part of greedy algorithms -> tons of questions revolve around priority queues
and it’s important to understand how we can use them. They support insert, delete, getMax(), and
other operations in logN time -> so instead of doing extra work with getting the max or min, we can use
heaps and make it faster.
88
30DaysCoding.co
It’s a heap based structure where we can sort and store elements in a min/max fashion so that
every time we need a new element -> we just pop it off from the top instead of sorting and
computing the whole thing again.
What is a heap? It’s a tree like structure with these conditions:
- Complete binary tree
- Min heap: Every node should be smaller than the ones below it. So the element at the top
(root node) will be the min one.
- Max heap: Every node should be bigger than the ones below it. So the element at the top
(root node) will be the max one.
⭐ Priority queues are also heavily used in graph theory -> where we want optimal paths or cheaper
things. For eg: Cheap tickets from point A to B. We can store the edges in a priority queue as we iterate
and then return the top path (with some other conditions).
Djikstra’s algorithm is a common one, where priority queue is used as the main data structure. It’s used
to find shortest paths between nodes in a graph. Imagine an airplane flight network where we want the
cheapest flight path. There are multiple short path algorithms which come under the banner of graph
theory, where most of them have to do something with priority queues. So it boils down to the
fundamental knowledge of BFS/DFS and how to add some tweaks for short paths, priority queue and
other things.
89
30DaysCoding.co
Bucket sort: “Bucket sort, or bin sort, is a sorting algorithm that works by distributing the elements of
an array into a number of buckets.”
We just iterate over the frequency map and add our items to the buckets for every frequency. Notice
here, how we have buckets for frequency and every bucket is an array list where we add the key. So it’s
the other way round. Eventually, the bucket would look like this:
3 -> 1...more elements with frequency 1
2 -> 2… more elements with frequency 2
90
30DaysCoding.co
We can also use a priority queue or a min heap and add/store elements in there, which does the sorting
for us. Remember, whenever there’s something to do with min/max or return a list of elements in
some sort of order -> priority queue can be very useful. Here’s how the code would look like for a heap
Make sure to understand the solution, make a small document for yourself, and your notes there. If you
have any additional questions, email us at 30dayscoding@gmail.com.
91
30DaysCoding.co
We can use a greedy approach here, to simply use the maximum denomination first, so starting off with
$25 notes and using those until we can’t and then using the others.
Part II
Here’s the catch, for example you have {$13, $25} bills and the total bill is $26. What do you do? If you
use a greedy approach, you would end up using the $25 and then leave the $1 behind. You gotta pay
that, or wash the dishes. Here’s where we would need to explore other options and the backtracking
hits us. Make sure you see both the parts here, and not just one.
Read
- Non Overlapping Intervals. This week I encountered many interval… | by Osgood Gunawan |
The Startup
- When to use Greedy Algorithms in Problem Solving
Videos
- Interval Scheduling Maximization (Proof w/ Exchange Argument)
- 3. Greedy Method - Introduction
Questions
- Leetcode-Largest Number
- Graph Coloring Problem – Techie Delight
- 435 Non-overlapping Intervals
- 787. Cheapest Flights Within K Stops
- Greedy
Tries
92
30DaysCoding.co
Introduction
⭐ Tries are also a type of prefix trees which are tree-like structures to store strings.
Let's start with a question: You have 2 strings and we want to find the common letters in it.
The first brute force way is to iterate over the first string, add the letters to a set -> then iterate over the
next string and see all the elements that are in the set. You could also do things like
string2.contains(char) -> but it’s the same thing wrt time complexity.
We can insert and find strings in O(L) time, where L is the length of the string. Another use case can be
to print the characters in order.
First, we want to decide how the Node class looks like. Every node needs to hold a map of the children
and a boolean which tells if it is the last node (leaf node / last character):
class TrieNode:
def init (self):
self.children =
93
30DaysCoding.co
self.isLast = False
We need the Trie class now. The major functions are insert, search, startsWith -> where we can also add
more -> delete, findChar, etc. Let’s begin the insert function.
Here’s a great article before moving forward: Trying to Understand Tries. In every installment of this
series… | by Vaidehi Joshi | basecs
Insert
⭐ We want to insert a character at the very end of the trie. The first part of that is iterating down and
finding the last character (through the isLast field of TrieNode) and then add the character to the map.
The letter which we’ll add will be a TrieNode() and not just a character. Every node is a TrieNode ->
which has those 2 things.
Here’s how we do it
- Iterate over the word - every letter
- Iterating forward -> node = node.children[letter]
- We add the letter there -> node.children[letter] = TrieNode()
94
30DaysCoding.co
- If we reach the end without returning false, we return if it’s the last element or not -> using the
isLast class field.
def search(self,
word): node =
Starts With
self.root for
⭐ We want to return
letter true if the string (prefix) is at the start of a word. We can simply use the class
in word:
field to our advantage
if letter andnot
find the
in right answer here.
node.children:
Here are the stepsreturn False
- Iterate over the letters
- If thenode
letter =is not in node.children -> return false. Remember, node.children is a dictionary of
node.children[letter]
the letter mappings for the children, -> so it should be there.
- Iterating forward -> node = node.children[letter]
return True
95
30DaysCoding.co
Questions ❓
- Leetcode 208. Implement Trie (Prefix Tree)
- Leetcode 139. Word Break
- Leetcode Word Break II
- Leetcode 212. Word Search II
- Leetcode 1032 Stream of Characters
- Leetcode 421 Maximum Xor of Two Numbers in an Array
Additional
⭐ These areTopics
some random mixed questions, which will teach you something new to learn. We should
never solve a question expecting it to come in our interview (even something similar), but to learn
something new from it!
Remember, we’re not trying to solve hundreds or thousands of questions, but to
- Understand the concepts
- Build problem solving skills
- Enjoy our time with questions
- Become a better developer
Kadane’s algorithm
Wikipedia: Maximum subarray problem
⭐ It’s used to solve the maximum subarray problem and the concept is to keep a track of the sum as
you go -> and change it to 0 when it’s negative. (so you’re positive at the very least). An edge case is all
negative numbers -> where you return the min of those.
96
30DaysCoding.co
Djikstra’s algorithm
⭐ Djikstra’s algorithm is a shortest path algorithm, where priority queue is used as the main data
structure. Imagine an airplane flight network where we want the cheapest flight path from point A to B.
There’s also the shortest-path-tree which basically returns a tree with lowest cost from one node to
another. So instead of just a short path from A to B, we do it for all the nodes in the graph.
Here’s a nice video about this algorithm: Dijkstra's Algorithm - Computerphile
97
30DaysCoding.co
Q.add_with_priority(v, dist[v])
Credits: Wiki
AVL Trees
In a normal BST, the elements in the left tree are smaller than the root and the right ones are bigger
than the root. It’s very useful for sorting and we can find the element in O(logN) time. There’s a catch ->
for the given nodes in an array -> there’s a format that we have to follow which generates multiple
binary trees with different structures.
[1,2,3] can generate a binary search tree with the root 3, left child 2, with left child 1 -> this is not what
we wanted and hence we need something better.
AVL trees have a condition, the balance factor has to be in the range {-1,0,1}. So it’s a self balancing
binary search tree.
Resources:
- 10.1 AVL Tree - Insertion and Rotations
98
30DaysCoding.co
Sorting
Sorting is super important as a concept but not super important in terms of knowing everything about
them. For questions, you can use .sort() to sort whatever you’re using, and rarely you’ll be asked to
actually implement the underlying algorithms. Read more here: Sorting algorithm
Here’s a great visualizer for all sorting algorithms: Sorting Algorithms Animations
Another one more: Brute Force - Bubble
More
If you think we should add a section or anything in general, please write to us at
30dayscoding@gmail.com
Additional
QuestionsAwesomeness
150 Questions: Data structures
Striver SDE Sheet
Blogs
How to make a computer science resume
How to apply for Internships and Jobs
How to take a technical phone interview
How to find coding projects + people
How to learn a language/framework from scratch
How to revise for Coding Interviews in 15/30/45 days
99
30DaysCoding.co
Youtubers
DSA
WilliamFiset (English)
IDeserve (English)
Kevin Naughton Jr. (English)
Back To Back SWE (English)
Tech Dose (English)
Codebix (Hindi)
Competitive coding
SecondThread
Errichto's Youtube channel
William Lin
Websites
30DaysCoding
Geeks for geeks
Leetcode Patterns – Medium
Interview Question
10