Complete Data Structures and Algorithms Roadmap: Resources, Notes, Questions, Solutions
Complete Data Structures and Algorithms Roadmap: Resources, Notes, Questions, Solutions
com
In this document, we’ve covered all the amazing data structures and algorithms that you need
to study for interviews. Reviewing these topics will make you a be er problem solver, a be er
developer, and help you ace your next technical coding interviews. If you have any questions
along the way, feel free to reach out by emailing [email protected].
Our Aim
- Our aim is to help you become a be er problem solver by gaining knowledge of
di erent data structures, algorithms, and pa erns.
- We want you to rst understand the concepts and visualize what’s going on, then you
can 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. It’s impo ant to understand the core
concepts and then work on extra stu .
There are thousands of questions out there that you can solve, but computer
science and coding is much more than just learning data structures. Building and developing
something is the core of computer science. So, if you’re actually interested in computer
science, then most of your time should go in learning new frameworks and building stu .
Another impo ant aspect of coding is to explore! The more you explore -> the closer you get
to your interests. Good luck!
Practice
- Practicing 150-200 questions will give you the con dence to approach new problems.
Solving only 2 questions for 75 days is not a lot if you think about it!
30DaysCoding.com
- Consistency is the key. Finish this guide in 75-90 days. Don’t rush it from today. Take
your time, revisit topics a er a while, read and watch a lot of videos, explore things out
there to eventually become a be er version of yourself.
- Enjoy the process and sta today. I’m sure you’ll do great. Have fun.
_______________________________________
_______________________________________
Arrays
Introduction
Informally, an array is a list of things. It doesn’t ma er what the things are; they can be
numbers, words, apple trees, or other arrays. Each thing in an array is called an item or an
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
- h ps://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
Let’s discuss some of the most common pa erns that concern arrays. 2D matrices are also
arrays and are very commonly asked about in interviews. A lot of graph, DP, and search based
questions involve the use of a 2D matrix. We’ve discussed pa erns such as these in each
section below. It’s impo ant to understand the core concepts so make sure to check it out.
Resources
Questions
- 1. Two Sum
- 771. Jewels and Stones
- Leetcode : How Many Numbers Are Smaller Than the Current Number
- Pa ition Labels
_______________________________________
_______________________________________
2 pointers
For questions where we’re trying to nd a subset, a set of elements, or something in a
so ed array -> 2 pointers approach is useful.
30DaysCoding.com
Some common questions with this approach are concerned with spli ing something or nding
something in the middle, eg: middle element of the linked list. This is something you will
instantly recognize a er solving some questions on it. So, think of the 2 pointers approach with
these types of problems and sta solving.
Here’s a general code template for solving a 2 pointer approach problem. We move from the
le and right with di erent conditions until there’s something we want to nd.
right-=1;
}
}
return false;
}
Kidding, let’s discuss. One brute force way is to have a separate array, iterate over the original
array and then add the items other than value to the new array. Now just return the new array.
But we have to do this in place, so we can’t create an additional array. What can we do instead?
Let’s sta from the beginning. If the number is something other than the ‘value’, let’s just bring
that to the beginning. Then, nally, we return the pointer.
Think of this -> we want to shi the elements behind if they don’t match the value given.
Pa II
What if we don’t have a value and just want to remove the duplicates and return the index?
30DaysCoding.com
We would still have 2 pointers, test if slow != fast -> move the slow pointer forward, and change
the nums[slow] to the fast one -> basically pushing that element back.
However, the logic would be more complex if the array is not so ed. We can simply store the
elements in a hashmap as we go, and eventually return when we nd target-nums[i] in the
array as we’re going forward.
while (i < j) {
if (A[i] + A[j] == X)
return true;
Sliding window problems are also a subtype of 2 pointers, where we use them to expand or
shrink the window with a condition. In general, it’s nice to nd pa erns but also remember that
a problem can be solved using multiple ways! We’ve discussed more in detail below.
Read
- A icle: 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. So Array By Parity II
- Reverse String
- Valid Palindrome
- E26. Remove Duplicates from So ed Array
- 75. So Colors
- 11. Container With Most Water
_______________________________________
_______________________________________
30DaysCoding.com
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-like thing 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 di erence 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.
This solution is absolutely correct, but it requires you to have a separate set (space
complexity), can we do something be er? 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 nd a
cycle, if not -> there’s no cycle.
Using slow and fast pointers:
while(
head!=null && slow.next !=null
&& fast.next!=null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
if(slow == fast) return true;
head = head.next;
}
30DaysCoding.com
Even simpler:
Pa 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 pa icular node itself.
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;
else:
cur.next = l2
l2 = l2.next
What do we do once a list is done and the other one is le ? Simply move the new linked list to
the next pointer -> cur = cur.next and then add all the elements of the le over list.
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 sho er way?
2. We can be sma about it and add all the lists into one big array or list -> so the array
-> then put the elements back in a new linked list!
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 de nition of linked list
- Introduction to Linked List
Videos
- Data Structures: Linked Lists
- Interview Question: Nth-to-last Linked List Element
30DaysCoding.com
Questions
- 141. Linked List Cycle (Leetcode)
- Delete Node in a Linked List"
- 19. Remove Nth Node From End of List
- Merge Two So ed 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 So ed Lists
Sliding Window
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 le as we iterate through
the string/array.
30DaysCoding.com
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
int max_sum = 0;
int window_sum = 0;
/* calculate sum of 1st window */
for (int i = 0; i < k; i++) {
window_sum += arr[i];
}
30DaysCoding.com
A must need a icle which covers more about this topic: Leetcode Pa ern 2 | Sliding
Windows for Strings | by csgator | Leetcode Pa erns
Think of this as expanding the window and shrinking it once we go out of bounds.
Now, how do we expand or shrink here? We expand when we’re exploring, so pre y much
always when we add an element to our search horizon - we increase the end variable. Let’s
take this step by step.
We have 2 pointers, sta /end, and a map -> we add elements with the ‘end’ pointer and
take out elements with the sta pointer (shrinking)
while(end< tree.length){
int a = tree[end];
map.put(a, map.getOrDefault(a,0)+1);
if(map.get(a)==1)counter++;
end++;
# something something
}
30DaysCoding.com
Let’s take the end pointer till the array length, add the element to the map and then while
the number of unique fruits are more than 2, remove the element from the map
while(counter>2){
int temp = tree[start];
map.put(temp, map.get(temp)-1); # remove elem count
if(map.get(temp)==0)counter--; # decrease counter
start++; # increment start
}
Now, we want to store the maximum window size at all times -> a er the second loop has
exited and we’re in the nice condition -> maximum 2 unique fruits. The conditions can be
changed here easily according to the number of unique fruits or min/max given to us.
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 a icle which covers more about this topic: Leetcode Pa ern 2 | Sliding
Windows for Strings | by csgator | Leetcode Pa erns
30DaysCoding.com
A template which can also help in solving these problems: Minimum window substring
template
Read
- Leetcode Pa ern 2 | Sliding Windows for Strings | by csgator | Leetcode Pa erns
- 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
- 239. Sliding Window Maximum
_______________________________________
_______________________________________
30DaysCoding.com
Binary Search
Introduction
We use binary search to optimize our search time complexity when the array is so ed (min,
max) and has a de nite 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 so ed, then nding an element shouldn’t require us to iterate
over every element where the cost is O(N). We can skip some elements and nd the element in
O(logn) time.
Algorithm
We sta with 2 pointers by keeping a low and high -> nding the mid and then comparing
that with the number we want to nd. If the target number is bigger, we move right -> as we
know the array is so ed. If it’s smaller, we move le because it can’t be on the right side,
where all the numbers are bigger than the mid value.
{
// find the mid-value in the search space and
// compares it with the target
int mid = (left + right) / 2;
// key is found
if (x == A[mid]) {
return mid;
}
Here’s a beautiful visualization to understand it even more: Branch and Bound - Binary Search
Let’s understand the recursive solution now: we call the function for the le side and right side
if the mid doesn’t match our target. We can either change the le /right pointers through the
arguments or through cases -> arguments looks like an easier way. If we want to move to the
right, we change the le pointer to mid+1, and if we wanna go le , we change the right pointer
to mid-1.
binarySearch(arr, l, mid - 1, x)
binarySearch(arr, mid + 1, r, x)
else:
# Element is not present in the array
return -1
Let’s discuss some intro level questions which can be solved by just the generic template for
the binary search algorithm.
Intro Problems
- Leetcode-First Bad Version
Find the rst element which is bad -> simply use the binary search template.
- Sq (x)
30DaysCoding.com
Find the square root of the number given. We can skip iteration over all the numbers and can
simply take the middle and go le 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 ts at the end. Return that. Thinking a li le more, we see
that we have a range (so ed) 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():
max_font = 0
start, end = min_font, max_font
30DaysCoding.com
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 max_font
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.
Now we want to nd the pivot and then also write the binary search algo - which will be the
cliche binary search algorithm.
A er some more digging, you’ll realize that this can be done with a single binary search
method as well. Let’s discuss that ->Instead of checking the mid with target (as done in a
generic binary search), we check the mid with sta and end -> cause the array is disto ed ->
so rst we want to condition on that.
Let’s say the nums[sta ] is less than the nums[mid] -> we get our new sta and end -> the
sta and mid. We get this condition:
So we just add one more condition to the already existing binary search conditions. We shi
the sta and end pointers a er we’ve discovered the subarray where we need to shi . Here’s
the full code:
if (nums[mid] == target)
return mid;
Similar Pa erns
There are other, advanced use cases of binary search where we want to nd 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’ owers. It doesn’t look like a problem which can be solved
using binary search, but it can be. We can o en de ne a new function which does additional
condition mapping for us and then helps us nd the middle.
Here’s a generic template and some awesome information to binary search questions and
identify problems where there is a limit de ned. Binary search template.
Read
- Lecture 5 MIT : Binary Search Trees, BST So | Lecture Videos
- Binary search cheat sheet for coding interviews. | by Tuan Nhu Dinh | The Sta up
- Binary Search Algorithm 101 | by Tom Sanderson | The Sta up
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 Inse Position
- Leetcode #33: Search in Rotated So ed Array
- 34. Find First and Last Position of Element in So ed 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
- Median of Two so ed arrays
_______________________________________
_______________________________________
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 pa s until you reach the top.
Another example is that you want to eat ‘15 bu er 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.
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 gure out them
- What happens when the function is called in the middle of the whole recursive
function?
- What happens to the stu below it?
- What do we think of the base case?
- How do we gure out when to return ?
- How do we save the value, specially in the true/false questions?
- How does backtracking come into place, w 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.
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) a er that would print 10 and
then 5 and so on. Think of it as going back to the top of the function, but with di erent
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 bonacci, where we want to return
the sum of the last 2 elements for the current element -> the code is something like b(n) +
b(n-1) where b() 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 b(n)
to return a number. This is a basic example, but it helps you gain some insights on the recursive
pa 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.
In simple words, we want to print out all the possible cases -> valid parentheses can be
generated.
One thing which strikes me is -> we need a way to add “(” and “)” to all possible cases and then
nd a way to validate so that we don’t generate the unnecessary ones.
The rst condition is if there are more than 0 open / le brackets, we recurse with the right
ones. And if we have more than 0 right brackets, we recurse with the le ones. Le and right
are initialized at N - the number given.
if(left>0){
parentheses(list, s+"(", right, left-1);
}
if(right>0){
parentheses(list, s+")", right-1, left);
}
There’s a catch. We can’t add the “)” everytime we have right>0 cause then it will not be
balanced. We can balance that with a simple condition of le <right.
Base case? When both right and le are 0? -> cause we’re subtracting one as we go down to
0. Here’s the nal thing:
if(left>0){
dfs(list, s+"(", right, left-1);
}
if(left<right && right>0){
dfs(list, s+")", right-1, left);
}
}
Here’s a nice video with the explanation: Reverse a Linked List Recursively
We can also solve this recursively and it’s a great way to understand it in a be er way. Here’s
how we do it:
- Store the recursive call in a node -> This takes the pointer to the end
- Point the curr’s next pointer to that
- Point head’s next to null -> this will be the tail (at every instance)
It’s not a super impo ant thing to know, but a nice-to-have as a concept when you’re
preparing.
These are problems and pa erns 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 nd 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 false;
if(n%3==0)
return isPowerOfThree(n/3);
if(n==1)return true;
return false;
}
if n==0:
return False
if n==1:
return True
if n%2!=0:
return False
return isPowerOfTwo(n//2)
combo(combination+letter, digits[1:])
We do this for every le er and add a base case for adding the combination to the result array.
Here’s how the complete code looks like
else:
for letter in phone[digits[0]]:
combo(combination+letter, digits[1:])
Here’s a java solution code for it: My recursive solution using Java
Let’s also look at an iterative way of solving this. We can simply take a Queue and use BFS (so
of) to iterate and then add the le ers when the conditions are true. We can iterate over the
digits, add the possible combinations if the size is valid.
30DaysCoding.com
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
pa erns in that section, so de nitely follow that a er this.
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 Pa I
- Explore: Leetcode Pa II
- 150 Questions: Data structures
Extra
- Complex Recursion Explained Simply
- Recursion Concepts every programmer should know
_______________________________________
_______________________________________
30DaysCoding.com
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 a er that, so you just come back and visit other places. Here’s a nice way to
think of any problem:
- Recognize the pa ern
- Think of a human way to solve it
- Conve it into code.
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 pa ern here. We can sta from the le - add the rst
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 pa ern with others.
How do we conve 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
30DaysCoding.com
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;
}
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
sho en the array every time and then have the base case as len(arr) == 0.
Problem 2: Subsets
h ps://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 sho er or anything, Just explore all the possible
options.
We usually make a second function which is recursive in nature and call that from the rst one
-> it’s easier, cleaner, and more understandable. There are ce ain ways of doing it in the same
function, but this is be er.
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...
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
- We recurse 2 times - with and without the element -> which is the niche of
backtracking, where we have a CHOICE
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]
recur(acc, ns, path, index + 1);
}
Read this carefully before moving forward. It’s impo ant 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 Pa itioning)
A good thing to note here is that we pass in the target_le - nums[i] which basically means that
we’re choosing that element and then subtracting that from what we have in the argument. So
the base case with this would be
Target_le == 0 -> because that’s when we know we can make the target.
One other thing to save some time and memory can be target_le < 0 -> to return when we
reach here, because negative numbers can never become positive numbers. So once the
target_le is below 0, it can never come up -> good to just return;
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 pa ern, where the thinking goes like this -> we want to explore all possible ways such
that eventually we nd an optimal thing, where queens don’t interact with each other.
We sta by placing the rst, then second… until there’s a con ict. We then would have to come
back, change the previous queens, until we nd the optimal way. We would have to go back to
the very sta as well, and maybe try the whole problem again.
How to conve this into code?
Similar to most backtracking problems, we will follow a similar pa ern:
- 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
Make sure to think about the base cases, recursive calls, the di erent 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:
30DaysCoding.com
- If you want to climb Mount Everest, you can recursively climb the smaller pa s 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 rst 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.
if stored_value[i]:
return stored_value[i]
// do something (recursive call)
stored_value[i] = value
}
Read
- A deep study and analysis of Recursive approach and Dynamic Programming by solving
the most…
- Leetcode Pa ern 3 | Backtracking | by csgator | Leetcode Pa erns
- A general approach to backtracking questions in Java (Subsets, Permutations,
Combination Sum, Palindrome Pa itioning)
30DaysCoding.com
- WTF is Memoization. Okay, those who saw this term for the… | by Leo Wu | Medium
Questions
- Word Search
- Leetcode #78 Subsets
- 90. Subsets II
- Le er Case Permutation
- 17. Le er 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
_______________________________________
_______________________________________
BFS, DFS
Introduction
These are searching techniques to nd 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)
30DaysCoding.com
Try to understand the iterative way of solving a DFS or BFS question and how things work.
There are 3 basic things
- Push the rst node
- Iterate over all nodes ( rst time it’s just the root)
- Pop the top element
- Add the neighbors
- Repeat (Usually through the while or for loop)
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:
while len(stack)>0:
node = stack.pop() # pop the grid item
if(node == target):
return true
30DaysCoding.com
# explore more
# For trees -> if root.left or root.right
if (condition):
stack.append(new_item)
return false;
The second step is that of MEMOIZATION 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 node using an array discovered []
This could be anything - array, map, set - depending on the situation. The only thing we need is
to store the visited things so that we’re not repeating any work.
q.add(u);
}
}
}
}
Trying to think of a recursive way to do this is also very impo ant. We call dfs for every node
a er exploring the neighbors and can do that in a couple of ways -> inside the for loop or
outside the for loop a er adding the neighbors to a list. Here’s an approach, also linking other
approaches below.
recursiveBFS(graph, q, discovered);
}
dfs, bfs and this is one of those things, which is usually used with a combination of things. For
instance, you have a 2D matrix with something inside it, and you want the sho est path ->
boom, BFS. Or maybe you have a graph where you want to nd the ve ex of it -> boom,
DFS/BFS. So it comes in many forms, and it’s very impo ant to understand it completely before
moving forward.
Here are some implementations and use cases for DFS, BFS:
DFS:
- Find connected components in a graph
- Calculate the ve ex 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
- Sho est path algorithms and questions
- Ford fulkerson algorithm
- Finding nodes in a graph
- Wherever there is a sho est thing/ nding something with the cheapest thing, etc.
Understanding this will de nitely open your eyes about the visualization that happens in a
search algorithm, let’s go!
We have a 2d matrix, with 0’s and 1’s or some other symbols. We want to nd the islands ->
where one island is one of more grid nodes which are connected together. Here’s an example:
Input: grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
Output: 3
We want to connect all the 1’s together, so that we form an island and then count those islands.
A 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 sta from the 1st node, pop it, mark it visited,
explore all the neighbors, and then repeat. Once this exploration is done, we sta with another
1, explore all of it’s connected 1’s and then mark those visited.
Every time we explore a new node island, we increase the count by 1 and eventually return that
number. Sounds easy? Go code it rst… I’m waiting.
Glad you’re back, let’s solve this both iteratively and recursively.
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
Pa ern: 2D Matrix
There are tons of problems where there’s something to nd 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.
30DaysCoding.com
There’s nothing special here, but it’s good to notice how we can take [0,0] as the root and
basically conve 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.o er(new Pair(i, j));
while len(stack)>0:
row,col = stack.pop() # pop the grid item
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 ge ing the size of the queue and then iterating over it
every time.
list.add(node.val);
if(node.left!=null)
queue.add(m.left);
if(node.right!=null)
queue.add(m.right);
}
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);
}
solution.add(new ArrayList<>(list));
}
return solution;
Problem 3: Ro en oranges
994. Ro ing Oranges
30DaysCoding.com
Every minute a fresh orange turns ro en if it’s around a ro en orange. Similar to life -> if
you’re around negative people, you tend to be negative. Keep a positive outlook, help
everyone, and take things forward!
This is an amazing question -> let’s understand the iterative way of doing this and how to solve
any searching related question with a stack or queue -> iteratively. We have the minimum
condition here, so using BFS is the way to go! A simple pa ern, as discussed before is:
- Prepare the stack/queue -> Add the initial nodes
- Pop the node from stack, mark it visited, add the valid neighbors
- Repeat the process for the new nodes.
First step is to prepare the queue. We add the ro en oranges (represented by 2) to the queue
and also count the total number of oranges. 0 -> means an empty place.
We have the queue ready and now we iterate until it’s empty: while (stack.isEmpty()) {}. We
want to add all the neighbors of the current orange, which are in 4 directions and here’s
something to note when you have conditions like this.
When we want to traverse in all 4 directions, or maybe in 8 directions if we have a double
condition, we can make a directions dictionary and iterate over it. Something like: [[0,1], [0,-1],
[1,1], [1, 0]] or int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};
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++;
30DaysCoding.com
// something
}
rotten_oranges++;
}
}
Read
- Leetcode pa erns 1
30DaysCoding.com
- Leetcode Pa erns 2
- Depth-First Search (DFS) vs Breadth-First Search (BFS) – Techie Delight
Videos
- Breadth First Search Algorithm | Sho est Path | Graph Theory
- Depth First Search Algorithm | Graph Theory
- Breadth First Search grid sho est 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. Pe ect Squares
- Course Schedule
- C/C++ Program for Detect cycle in an undirected graph
- 127. Word Ladder
- 542. 01 Matrix
- Ro ing Oranges
- 279. Pe ect Squares
- 797. All Paths From Source to Target
- 1254. Number of Closed Islands
_______________________________________
_______________________________________
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 rst. You de nitely 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 con dent a er that.
So if you’ve skipped the recursion, backtracking, and memoization section -> go back and
complete those rst! If you’ve completed it, keep reading. You will only get be er at dynamic
programming (and problem solving in general) by solving more recursion (logical) problems.
Problem 1: 01 Knapsack
This is the core de nition of dynamic programming. Understanding this problem is super
impo ant, so pay good a ention. 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 rst glance, I see that maxWeight will help us with the base case. At every step,
we have 2 CHOICES:
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 di erent arguments, it’s about
making things easier.
We think of the base case now. A straigh orward one looks like maxWeight === 0, which is also
the REMAINING weight as we’re subtracting the weight every time we’re iterating with the
included item.
30DaysCoding.com
The second one and the most usual one is when you reach the end of the array, so index ===
weights.length. Can also be values.length as they’re the same.
Here’s the code for it:
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.
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 impo ant 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.
30DaysCoding.com
current_sum = grid[row][col]
max_sum = min(
current_sum + function(grid[row+1][col]),
current_sum + function(grid[row][col+1]
))
Now we can make it even easier by just passing the rows and columns instead of the whole
grid and bringing out some things to clean it.
current_sum = grid[row][col]
max_sum = current_sum + min(function(row+1, col),function(row, col+1]))
Instead of calculating the sum at every step, we pass it back to the recursive function who
does the 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.*/
30DaysCoding.com
/** When we reached the first column, we could only move vertically.*/
if(col == 0) return grid[row][col] + min(grid, row - 1, col);
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:
We need to have a way to change the ‘days’ such that -> if we choose option 1 (1 day), we want
to move to the next POSSIBLE day. If we choose option 2 (7 days), we want to move to the next
POSSIBLE day within 7 days and the same with 30 days. So there’s a condition before we
recurse every time -> we want to change the current_day variable
Here’s the condition:
Base case? When the index or the current_day goes beyond the days array
This can be di erent 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;
int k = i;
for(; k <days.length; k++){
if(days[k] >= days[i] + 7){
break;
}
}
int option_7days = costs[1] + rec(days, costs, k, dp);
There are a row of n houses, each house can be painted with one of the three colors: red,
blue or green. The cost of painting each house with a ce ain color is di erent. 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
use memoization for repetitive work and we’ll have our answer. Sounds easy? … no it’s not.
Come on, when did DP become easy?
Just kidding, let’s make it easy. 3 choices? 3 recursive options -> inse , 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 inse ing ->
adding a le er in one string, means deleting something from the other (in a way), so we can
mix and match the deleting/inse ion operations. Coming to update -> that just means we’re
changing that le er and moving forward, so the recursive call will be rst[1:] and second[1:].
Here’s how the recursive calls look like:
Base case? You forgot right? Well, forget ge ing 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 nishes
(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)
return first_string.length();
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 nal result.
Watch this awesome visualization: Dynamic Programming - Levenshtein's Edit Distance
Here’s the combined result:
if(first.length() == 0)
return second.length();
30DaysCoding.com
Read
- My experience and notes for learning DP
- Dynamic Programming (Theory - MIT)
- Dynamic Programming (Theory MIT)
- Dynamic Programming Pa erns
Videos
- MIT Playlist: 19. Dynamic Programming I: Fibonacci, Sho est 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
30DaysCoding.com
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 Pa ition Equal Subset Sum
- 198. House Robber
- Leetcode - Decode Ways
- 139. Word Break
- LeetCode – Edit Distance
- 300. Longest Increasing Subsequence
- 787. Cheapest Flights Within K Stops
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 di erent things, for di erent
reasons, and then use them to our advantage. Here’s a nice depiction of how the actually look:
30DaysCoding.com
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 de nitely make sure you understand it completely. You
should be seeing this visually in your head, when you do it iteratively.
Pa ern: 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 le or right node (but the subtree as a whole).
Inorder traversal
Let’s sta with inorder traversal: We de ne a stack and will traverse the tree iteratively.
Recursive solutions to these 3 basic ones are pre y straigh orward, so we’ll try to understand
them a li le more with iterative ones.
We sta with the root, move until it’s null or the stack is empty. We move to the le if we can, if
not -> we pop, add the popped value and then move right.
30DaysCoding.com
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);
}
}
30DaysCoding.com
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.
// 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
30DaysCoding.com
- Heap so ing
while (!queue.isEmpty()) {
minimumTreeDepth++;
int levelSize = queue.size();
for (int i = 0; i < levelSize; i++) {
TreeNode currentNode = queue.poll();
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.
30DaysCoding.com
Having a parent node relation is impo ant here, so here’s the rst thing I think: We make a map
and store {parent: node} inside that map as we go down.
So we do a simple iterative DFS, store the parent node relation, and then come back to see that
relation to nd the common node.
Here we ll 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 nd the common ancestor. We make
a set, add the rst node in the set while iterating over the parent_map, then we check for the
30DaysCoding.com
node_2 in the set and when we nd that -> we break the while loop and return node_2 at that
point.
node_set = set()
while node_1:
node_set.add(node_1)
node_1 = parent_map[p]
while node_2 not in node_set:
node_2 = parent_map[node_2]
return node_2
It’s o en hard to come up with recursive solutions instantly, but over time - you’ll be more
comfo able (I’m not :P) to bring them up.
More solutions here: Lowest Common Ancestor of a Binary Tree
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:
convertToBST(root.left, it);
root.data = it.next();
convertToBST(root.right, it);
30DaysCoding.com
A solution video explaining the same: Conve ing Binary Tree to Binary Search Tree without
changing spatial structure
Read
- Leetcode Pa ern 0 | Iterative traversals on Trees | by csgator | Leetcode Pa erns
- Inorder Tree Traversal – Iterative and Recursive – Techie Delight
Videos
- Data structures: Introduction to Trees
- Binary Tree Bootcamp: Full, Complete, & Pe ect Trees. Preorder, Inorder, & Postorder
Traversal.
- 5. Binary Search Trees, BST So
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
- Same Tree
- Leetcode #112 Path Sum
- Leetcode #104 Maximum Depth of Binary Tree
- Leetcode #108 Conve So ed Array to Binary Search Tree
- Leetcode #98 Validate Binary Search Tree
- Binary Search Tree Iterator
- 96. Unique Binary Search Trees
- Serialize and Deserialize BST
30DaysCoding.com
_______________________________________
_______________________________________
Graphs
Introduction
A lot of graph problems are covered by DFS, BFS, topo so 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 so , and much more. You can think of those topics so of coming under the umbrella of
graph theory sometimes.
A human way of nding 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 impo ant pa 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:
So the solution here seems to be trivial -> we iterate over, nd the new nodes, mark the
neighbors visited, and then nally return the ve ex. DFS/BFS anything works -> let’s try to do it
recursively.
We have the theory of strongly connected components here, which is used to nd di erent
sets of nodes in a graph which are connected with each other -> which can be modi ed to
return a node with no ve ex.
30DaysCoding.com
Let’s analyse through code. We explore and call dfs on the nodes and keep a track of the last
node:
int last_node = 0;
for (int node = 0; node < N; node ++)
{
if (!visited[node])
{
DFS(graph, node, visited);
last_node = node;
}
}
Once we’re out of this loop, we have the last_node stored ->> which is our best bet of being
the ve ex. Now we can reset the visited array and check if the visited array has any more
nodes or not.
- Why are we rese ing the value of the visited array? Because we want to do a fresh search.
- Why are we checking for visited [i] -> because the ve ex should be the only one which was
not visited.
- Why are we returning -1 => because we didn’t nd the ve ex if there are more than 1 nodes
not explored -> meaning more than 1 ve ex.
30DaysCoding.com
Here’s an a icle, explaining more about this: Find root ve ex of a graph – Techie Delight
We want to color the nodes in a way that no 2 consecutive nodes have the same color. There
are a lot of implementations a ached to this concept, some of them are:
- Scheduling: Problems where we have a list of jobs or times or rooms, and we want to
nd an optimal way to nd the schedules of di erent things.
- Other seating related problems can also be solved using this approach - where we
don’t want any 2 people si ing next to each other.
30DaysCoding.com
Then we try to nd the color for the node. This will be a simple search algorithm, where we
iterate over and nd the color:
Once we nd the color -> node_color -> we add it to the node {node: color} and store it in a list.
Other questions and concepts in this range can be solved with a similar pa ern:
- Explore the nodes
- Find the neighboring colors/conditions
- Add those and store to nd the next best thing
Similar questions
- [849] Maximize Distance to Closest Person
30DaysCoding.com
Credits
- Graph coloring - Wikipedia
- Scheduling (computing)
So we need an additional condition here: To check for the parent node when writing Dfs code.
So how do we check for the parent?
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.
For iterative solution, we follow the simple DFS path with stack
- Add the node to the stack
- Add a condition -> In this case: check for the cycle
- Pop the node and explore the neighbors
- Add the valid neighbors to the stack
We can pass in a {root, parent} and then check once we pop it o the stack.
stack.push({node, parent});
while (stack.isEmpty()):
node, parent = stack.pop();
stack.append(neighbor)
We call this DFS function for every unvisited node, mark it visited in the DFS function, and then
increase the circles.
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.
DFS(node);
}
}
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 o en overlap with
some sho est path questions. So here are some links you can refer to, when studying about
these:
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
30DaysCoding.com
Questions
- Employee Impo ance
- Redundant Connection
- 130 Surrounded Regions
- 721. Accounts Merge
- Leetcode-Clone Graph
- Word Search
- Network Delay Time
- Is Graph Bipa ite?
- 802. Find Eventual Safe States
- 841. Keys and Rooms
- Leetcode : Possible Bipa ition
- [947] Most Stones Removed with Same Row or Column
- 994. Ro ing 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.
_______________________________________
_______________________________________
30DaysCoding.com
Topological So ing
Introduction
The name suggests so ing, so it probably should be :P. Here’s the de nition: “topological
ordering of a directed graph is a linear ordering of its ve ices such that for every directed
edge uv from ve ex u to ve ex v, u comes before v in the ordering”
In simple words, we need to so 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 so ing. You can make your class schedule using this algorithm.
Here’s a beautiful way to see topological so ing in action: Branch and Bound - Topological So
We have an array given to us, let’s iterate over that -> go as deep as possible and add that to
our set. We want to go to the last node and begin from there. Here’s the algorithm from
Wikipedia.
Removing edge means marking it visited and never coming back to it again. So here’s what the
rst thing looks like.
We have to de ne toposo () which does the same thing -> takes the pointer to the very last
node, adds it to the stack, marks them visited along the way, and then eventually lls up the
array. Here’s the toposo function:
if(visited[x] == false){
visited[x] = true;
toposort(x, visited, adj, stack);
}
}
stack.push(i);
}
A er lling up the stack, we print out all the items from that in a so ed form. Here’s a nice
explanation video by WIlliam Fiset on topo so : Topological So Algorithm | Graph Theory
Here’s another version of the code for topological so ing: Python Topological So ing,
[Topological So Algorithm]
Now that we know the basics of topological so ing, 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.
We want to call DFS on all the nodes as we go and mark them visited once we cover them ->
basics of topological so ing.
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 li le di erent as we’re returning true or false based on that pa icular 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 nd a negative result. If we don’t,
we return true at the end.
visited[course] = false;
return true;
}
Resources
- Topological So Graph | Leetcode 207 | Course Schedule
- [LeetCode]Course Schedule. May 30-Day Challenge | by Yinfang
- Course Schedule | Deadlock detection | Graph coloring | Leetcode #207
- Leetcode - Course Schedule (Python)
Read
- De nition: Wikipedia
- Visualizer: Branch and Bound - Topological So
Videos
- Topological So Algorithm | Graph Theory
- Topological So | Kahn's Algorithm | Graph Theory
Questions
- Topological So
- Leetcode : Find the Town Judge
- LeetCode 210. Course Schedule II
_______________________________________
_______________________________________
30DaysCoding.com
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 de nitely work, but it might not be the most optimal thing to do w time and space
complexity.
For instance, you have a tree and you want to nd the maximum path sum of that tree. The
correct solution to that would be to explore all di erent cases, add memoization to the logic,
and nally return 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 sma about using it at the
right time. Here are some sub topics which will help you understand things in a be er way.
A lot of questions can be solved by so ing the input and then adding some logic to that. Let’s
discuss a question: meeting rooms. We have the sta ing 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 so the times, arrange the people in terms of the time and then
nd the maximum while iterating through the instances. Let’s discuss some problems on
similar concepts.
Brute force looks annoying here, we iterate over, nd all possible cases, memoize something,
and then nally 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 li le here, what if we track every time someone comes in and
goes (a er so ing). So if we so this array -> we would see someone comes at 2,3,5 and
someone leaves at 4,7,7.
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.
rearrange_rooms.sort()
rooms=0
max_rooms=0
# iterate and add value to room
for pos, value in rearrange_rooms:
rooms += value
# store the max rooms
max_rooms = max(rooms, max_rooms)
return max_rooms
30DaysCoding.com
You can also manually +1 and -1 for the incoming and outgoing people, here we just transform
that into a big array with these elements: (incoming_time, +1) or (outgoing_time, -1)
See how so ing + greedy helps solve some amazing problems with ease. Always think of
so ing arrays if they can simplify the problems. The time complexity is NlogN for so ing which
o en helps in optimization.
// If, after being sorted, the largest number is `0`, the entire number
// is zero.
if (asStrs[0].equals("0")) {
return "0";
}
return largestNumberStr;
}
30DaysCoding.com
Priority Queue
Priority Queue is a big pa of greedy algorithms -> tons of questions revolve around
priority queues and it’s impo ant to understand how we can use them. They suppo inse ,
delete, getMax(), and other operations in logN time -> so instead of doing extra work with
ge ing the max or min, we can use heaps and make it faster.
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 nd sho est paths between nodes in a graph. Imagine an airplane ight network
where we want the cheapest ight path. There are multiple sho path algorithms which come
under the banner of graph theory, where most of them have to do something with priority
30DaysCoding.com
queues. So it boils down to the fundamental knowledge of BFS/DFS and how to add some
tweaks for sho paths, priority queue and other things.
Once you’ve added the items, we iterate from the back and return the k elements.
for (int pos = bucket.length - 1; pos >= 0 && res.size() < k; pos--) {
if (bucket[pos] != null) {
res.addAll(bucket[pos]);
}
}
We can also use a priority queue or a min heap and add/store elements in there, which does
the so ing for us. Remember, whenever there’s something to do with min/max or return a list
of elements in some so 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 [email protected].
Pa 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 go a 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 pa s here, and not just one.
Read
- Non Overlapping Intervals. This week I encountered many interval… | by Osgood
Gunawan | The Sta up
- When to use Greedy Algorithms in Problem Solving
30DaysCoding.com
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
Introduction
Tries are also a type of pre x trees which are tree-like structures to store strings.
Let's sta with a question: You have 2 strings and we want to nd the common le ers in it.
The rst brute force way is to iterate over the rst string, add the le ers 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 w time complexity.
We can inse and nd strings in O(L) time, where L is the length of the string. Another use
case can be to print the characters in order.
30DaysCoding.com
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 = {}
self.isLast = False
We need the Trie class now. The major functions are inse , search, sta sWith -> where we can
also add more -> delete, ndChar, etc. Let’s begin the inse function.
Here’s a great a icle before moving forward: Trying to Understand Tries. In every installment of
this series… | by Vaidehi Joshi | basecs
30DaysCoding.com
Inse
We want to inse a character at the very end of the trie. The rst pa of that is iterating
down and nding the last character (through the isLast eld of TrieNode) and then add the
character to the map.
The le er 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 le er
- Iterating forward -> node = node.children[le er]
- We add the le er there -> node.children[le er] = TrieNode()
node = node.children[letter]
node.isLast = True
Searching
node = node.children[letter]
return node.isLast
Sta s With
We want to return true if the string (pre x) is at the sta of a word. We can simply use the
class eld to our advantage and nd the right answer here.
Here are the steps
- Iterate over the le ers
- If the le er is not in node.children -> return false. Remember, node.children is a
dictionary of the le er mappings for the children, -> so it should be there.
- Iterating forward -> node = node.children[le er]
return True
Resources
- Trie Data Structure - Beau teaches JavaScript
- Trie Data Structure Implementation (LeetCode)
Questions
- Leetcode 208. Implement Trie (Pre x Tree)
- Leetcode 139. Word Break
- Leetcode Word Break II
- Leetcode 212. Word Search II
30DaysCoding.com
_______________________________________
_______________________________________
Additional Topics
These are 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 be er 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.
Djikstra’s algorithm
Djikstra’s algorithm is a sho est path algorithm, where priority queue is used as the main
data structure. Imagine an airplane ight network where we want the cheapest ight path from
30DaysCoding.com
point A to B. There’s also the sho est-path-tree which basically returns a tree with lowest cost
from one node to another. So instead of just a sho 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
Q.add_with_priority(v, dist[v])
30DaysCoding.com
Credits: Wik
AVL Trees
In a normal BST, the elements in the le tree are smaller than the root and the right ones are
bigger than the root. It’s very useful for so ing and we can nd 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 di erent structures.
[1,2,3] can generate a binary search tree with the root 3, le child 2, with le child 1 -> this is not
what we wanted and hence we need something be er.
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 - Inse ion and Rotations
- AVL tree - Wikipedia
- AVL Tree Visualization
30DaysCoding.com
So ing
So ing is super impo ant as a concept but not super impo ant in terms of knowing
everything about them. For questions, you can use .so () to so whatever you’re using, and
rarely you’ll be asked to actually implement the underlying algorithms. Read more here: So ing
algorithm
Here’s a great visualizer for all so ing algorithms: So ing Algorithms Animations
More so ing visualizer algorithms: Divide and Conquer - Bucket
More
If you think we should add a section or anything in general, please write to us at
[email protected]
_______________________________________
_______________________________________
Additional awesomeness
Questions
- 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 nd coding projects + people
- How to learn a language/framework from scratch
- How to revise for Coding Interviews in 15/30/45 days
- Everything about a technical internship
- How to choose the right university (USA)
30DaysCoding.com
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 Pa erns – Medium
- Interview Question