Skip to content

About

Several Coding Patterns for Solving Data Structures and Algorithms Problems during Interviews

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

114 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Several Coding Patterns for Solving Data Structures and Algorithms Problems during Interviews

This repository contains a collection of all the important problem-solving patterns to learn for LeetCode and coding interviews. The problems are categorized into a set of 26 patterns that show up constantly in technical assessments.

The 16 course patterns

Pattern 1: Sliding Window Pattern 9: Two Heaps
Pattern 2: Two Pointer Pattern 10: Subsets
Pattern 3: Fast & Slow pointers Pattern 11: Modified Binary Search
Pattern 4: Merge Intervals Pattern 12: Bitwise XOR
Pattern 5: Cyclic Sort Pattern 13: Top 'K' Elements
Pattern 6: In-place Reversal of a LinkedList Pattern 14: K-way merge
Pattern 7: Tree Breadth First Search Pattern 15: 0/1 Knapsack (Dynamic Programming)
Pattern 8: Depth First Search (DFS) Pattern 16: Topological Sort (Graph)

Additional patterns

Pattern 17: Trie (Prefix Tree) Pattern 22: Backtracking
Pattern 18: Union Find (Disjoint Set) Pattern 23: Shortest Path (Dijkstra & Friends)
Pattern 19: Monotonic Stack Pattern 24: Greedy Algorithms
Pattern 20: Prefix Sum Pattern 25: Bit Manipulation
Pattern 21: Island (Matrix Traversal) Pattern 26: Data Structure Design

Additional Resources

Here are a few other resources that I found helpful when learning Data Structures and Algorithms using Java

🌟 Challenge Questions

πŸ‘©πŸ½β€πŸ¦― Questions from Blind 75

πŸ˜πŸ“– Questions tagged FaceBook/Meta

🌴 Questions tagged Amazon

πŸ”Ž Questions tagged Google

In many problems dealing with an array (or a LinkedList), we are asked to find or calculate something among all the contiguous subarrays (or sublists) of a given size. For example, take a look at this problem:

Given an array, find the average of all contiguous subarrays of size K in it.

Lets understand this problem with a real input:

Array: [1, 3, 2, 6, -1, 4, 1, 8, 2], K=5

A brute-force algorithm will calculate the sum of every 5-element contiguous subarray of the given array and divide the sum by 5 to find the average.

The efficient way to solve this problem would be to visualize each contiguous subarray as a sliding window of 5 elements. This means that we will slide the window by one element when we move on to the next subarray. To reuse the sum from the previous subarray, we will subtract the element going out of the window and add the element now being included in the sliding window. This will save us from going through the whole subarray to find the sum and, as a result, the algorithm complexity will reduce to O(N).

In problems where we deal with sorted arrays (or LinkedLists) and need to find a set of elements that fulfill certain constraints, the Two Pointers approach becomes quite useful. The set of elements could be a pair, a triplet or even a subarray. For example, take a look at the following problem:

Given an array of sorted numbers and a target sum, find a pair in the array whose sum is equal to the given target.

To solve this problem, we can consider each element one by one (pointed out by the first pointer) and iterate through the remaining elements (pointed out by the second pointer) to find a pair with the given sum. The time complexity of this algorithm will be O(N^2) where n is the number of elements in the input array.

Given that the input array is sorted, an efficient way would be to start with one pointer in the beginning and another pointer at the end. At every step, we will see if the numbers pointed by the two pointers add up to the target sum. If they do not, we will do one of two things:

  1. If the sum of the two numbers pointed by the two pointers is greater than the target sum, this means that we need a pair with a smaller sum. So, to try more pairs, we can decrement the end-pointer.
  2. If the sum of the two numbers pointed by the two pointers is smaller than the target sum, this means that we need a pair with a larger sum. So, to try more pairs, we can increment the start-pointer.

The Fast & Slow pointer approach, also known as the Hare & Tortoise algorithm, is a pointer algorithm that uses two pointers which move through the array (or sequence/LinkedList) at different speeds. This approach is quite useful when dealing with cyclic LinkedLists or arrays.

By moving at different speeds (say, in a cyclic LinkedList), the algorithm proves that the two pointers are bound to meet. The fast pointer should catch the slow pointer once both the pointers are in a cyclic loop.

One of the famous problems solved using this technique was Finding a cycle in a LinkedList. Lets jump onto this problem to understand the Fast & Slow pattern.

This pattern describes an efficient technique to deal with overlapping intervals. In a lot of problems involving intervals, we either need to find overlapping intervals or merge intervals if they overlap.

Given two intervals (a and b), there will be six distinct ways the two intervals can relate to each other:

  1. a and bdo not overlap
  2. a and b overlap, b ends after a
  3. a completely overlaps b
  4. a and b overlap, a ends after b
  5. b completely overlaps a
  6. a and b do not overlap

Understanding the above six cases will help us in solving all intervals related problems.

This pattern describes an interesting approach to deal with problems involving arrays containing numbers in a given range. For example, take the following problem:

You are given an unsorted array containing numbers taken from the range 1 to n. The array can have duplicates, which means that some numbers will be missing. Find all the missing numbers.

To efficiently solve this problem, we can use the fact that the input array contains numbers in the range of 1 to n. For example, to efficiently sort the array, we can try placing each number in its correct place, i.e., placing 1 at index 0, placing 2 at index 1, and so on. Once we are done with the sorting, we can iterate the array to find all indices that are missing the correct numbers. These will be our required numbers.

In a lot of problems, we are asked to reverse the links between a set of nodes of a LinkedList. Often, the constraint is that we need to do this in-place, i.e., using the existing node objects and without using extra memory.

in-place Reversal of a LinkedList pattern describes an efficient way to solve the above problem.

This pattern is based on the Breadth First Search (BFS) technique to traverse a tree.

Any problem involving the traversal of a tree in a level-by-level order can be efficiently solved using this approach. We will use a Queue to keep track of all the nodes of a level before we jump onto the next level. This also means that the space complexity of the algorithm will be O(W), where W is the maximum number of nodes on any level.

This pattern is based on the Depth First Search (DFS) technique to traverse a tree.

We will be using recursion (or we can also use a stack for the iterative approach) to keep track of all the previous (parent) nodes while traversing. This also means that the space complexity of the algorithm will be O(H), where H is the maximum height of the tree.

In many problems, where we are given a set of elements such that we can divide them into two parts. To solve the problem, we are interested in knowing the smallest element in one part and the biggest element in the other part. This pattern is an efficient approach to solve such problems.

This pattern uses two Heaps to solve these problems; A Min Heap to find the smallest element and a Max Heap to find the biggest element.

A huge number of coding interview problems involve dealing with Permutations and Combinations of a given set of elements. This pattern describes an efficient Breadth First Search (BFS) approach to handle all these problems.

As we know, whenever we are given a sorted Array or LinkedList or Matrix, and we are asked to find a certain element, the best algorithm we can use is the Binary Search.

XOR is a logical bitwise operator that returns 0 (false) if both bits are the same and returns 1 (true) otherwise. In other words, it only returns 1 if exactly one bit is set to 1 out of the two bits in comparison.

Any problem that asks us to find the top/smallest/frequent K elements among a given set falls under this pattern.

This pattern helps us solve problems that involve a list of sorted arrays.

Whenever we are given K sorted arrays, we can use a Heap to efficiently perform a sorted traversal of all the elements of all arrays. We can push the smallest (first) element of each sorted array in a Min Heap to get the overall minimum. While inserting elements to the Min Heap we keep track of which array the element came from. We can, then, remove the top element from the heap to get the smallest element and push the next element from the same array, to which this smallest element belonged, to the heap. We can repeat this process to make a sorted traversal of all elements.

0/1 Knapsack pattern is based on the famous problem with the same name which is efficiently solved using Dynamic Programming (DP).

In this pattern, we will go through a set of problems to develop an understanding of DP. We will always start with a brute-force recursive solution to see the overlapping subproblems, i.e., realizing that we are solving the same problems repeatedly.

After the recursive solution, we will modify our algorithm to apply advanced techniques of Memoization and Bottom-Up Dynamic Programming to develop a complete understanding of this pattern.

Topological Sort is used to find a linear ordering of elements that have dependencies on each other. For example, if event B is dependent on event A, A comes before B in topological ordering.

The following patterns are not part of the original course, but they come up often enough in interviews to be worth their own notes.

A Trie (or prefix tree) stores a set of strings as a tree of shared prefixes, so every lookup costs O(L) in the length of the word rather than O(N*L) across the whole dictionary.

Reach for it whenever a problem repeatedly asks "is this a prefix of anything I know?" β€” autocomplete, wildcard word matching, or searching a grid for many words at once.

Union Find tracks which elements belong to the same group while the groups keep merging. With path compression and union by rank, both find() and union() run in near-constant amortized time.

This is the tool for dynamic connectivity questions and for detecting cycles in an undirected graph β€” the counterpart to Pattern 16: Topological Sort, which handles the directed case.

When a problem asks for the next (or previous) greater or smaller element, keeping a stack in sorted order lets every element be pushed and popped at most once, turning the obvious O(N^2) scan into O(N).

The same idea with a deque solves sliding-window extremes, which is why this pattern pairs naturally with Pattern 1: Sliding Window.

Precomputing cumulative sums turns any range-sum query into a single O(1) subtraction, and pairing prefix sums with a HashMap turns "count the subarrays summing to K" from O(N^2) into O(N).

Crucially, this works when the array contains negative numbers β€” exactly the case where the sliding-window approach of Pattern 1 breaks down.

A 2D grid is really an implicit graph: each cell is a node and its neighbours are computed by adding offsets rather than read from an adjacency list.

Once you accept that framing, the tree traversals from Pattern 7 and Pattern 8 apply unchanged β€” DFS to size a connected component, BFS whenever the question involves distance or simultaneous spread.

Backtracking explores a decision tree with a choose β†’ explore β†’ un-choose skeleton, and its real power is pruning: abandoning a branch the moment it cannot possibly lead to a solution.

Where Pattern 10: Subsets focuses on generating every combination, this pattern is about constraint satisfaction β€” N-Queens, Sudoku, word search.

Picking the right shortest-path algorithm is most of the work: plain BFS when every edge costs the same, Dijkstra for non-negative weights, Bellman-Ford when there are negative edges or a hop limit, and Floyd-Warshall for all-pairs on a small dense graph.

A Greedy algorithm commits to the locally-optimal choice and never reconsiders. The entire skill is recognising when that is actually safe β€” and when it quietly is not.

These notes deliberately include cases where the greedy instinct fails, and contrast them with Pattern 15: 0/1 Knapsack: fractional knapsack is greedy-solvable, 0/1 knapsack is not.

The broader bitwise toolkit beyond Pattern 12: Bitwise XOR β€” masking, shifting, the n & (n-1) and n & -n idioms, and enumerating subsets with a bitmask.

Includes the Java caveat for unsigned bitwise operations: Java lacks unsigned integers, so you must use the >>> operator for unsigned right shifts.

Design questions hand you a target complexity per operation and ask you to hit it. The craft is combining two structures so each covers the other's weakness.

The canonical example: a HashMap gives O(1) lookup but no ordering, a doubly-linked list gives O(1) insert/remove but no lookup β€” together they give an O(1) LRU cache.

About

Several Coding Patterns for Solving Data Structures and Algorithms Problems during Interviews

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages