Dsa Cheatsheet Mastery 1695728264
Dsa Cheatsheet Mastery 1695728264
Dsa Cheatsheet Mastery 1695728264
Introduction
This guide serves as a comprehensive introduction to data structures, a critical topic for anyone
looking to become proficient in computer science and software development. By the end of this
15-day journey, you will have a strong foundation and hands-on experience with various data
structures.
Target Audience
This guide is designed to cater to a broad audience, including but not limited to:
Learning Outcomes
1. Understand Various Data Structures: Gain a deep understanding of arrays, linked lists, stacks,
queues, trees, graphs, and more.
2. Analyze Algorithms: Be capable of analyzing the time and space complexities of algorithms
that manipulate these data structures.
3. Problem-Solving Skills: Develop the ability to solve complex problems related to data storage,
retrieval, and manipulation.
4. Hands-on Experience: Acquire practical skills through coding exercises, examples, and mini-
projects.
5. Preparation for Interviews: Be well-prepared for technical interviews that focus on data
structures and algorithms.
Day 1: Introduction to Data Structures
Topics Covered
In-depth Explanation
Exercises
Code Snippet:
my_array = [1, 2, 3, 4, 5]
Code Snippet:
class Node:
def __init__(self, data):
self.data = data
self.next = None
Explanation: The `Node` class represents a single node in a linked list. It has two properties:
`data`, which stores the value, and `next`, which points to the next node in the list.
-------------------------------------------------------------------------------------------------------------------------------
Basics of Arrays
An array is a collection of elements identified by index or key. They are one of the simplest data
structures and serve as the building blocks for more complex types. This section will cover the
basic properties, operations, and applications of arrays.
Manipulating Strings
Strings are arrays of characters. Understanding how to manipulate strings is crucial for tasks
ranging from parsing text to algorithms like pattern matching. This section covers common
string operations such as reversing, concatenation, and substring extraction.
In-depth Explanation
Basics of Arrays
• Properties: Fixed-size, contiguous memory allocation.
• Operations: Insertion, deletion, traversal, and search.
• Applications: Used in multiple dimensions in matrices, used as buffers in various data
stream algorithms.
Manipulating Strings
• Properties: Immutable or mutable based on the language.
• Operations: Finding length, substring, concatenation, and reversal.
• Applications: Text processing, string matching algorithms, and natural language
processing tasks.
Exercises
Code Snippet:
def reverse_array(arr):
return arr[::-1]
Explanation: This Python function takes an array as input and returns a new array with the
elements in reverse order.
Code Snippet:
def is_palindrome(s):
return s == s[::-1]
-------------------------------------------------------------------------------------------------------------------------------
In-depth Explanation
Exercises
Code Snippet:
class Node:
def __init__(self, data):
self.data = data
self.next = None
class SinglyLinkedList:
def __init__(self):
self.head = None
Explanation: The `Node` class represents the nodes in the list, while the `SinglyLinkedList` class
contains a head pointer to the first node in the list.
Code Snippet:
def delete_node(self, node):
if self.head is None:
return
if self.head == node:
self.head = node.next
if node.next is not None:
node.next.prev = node.prev
if node.prev is not None:
node.prev.next = node.next
Explanation: The `delete_node` method deletes a given node from a doubly linked list. It adjusts
the next and previous pointers of adjacent nodes to remove the target node.
-------------------------------------------------------------------------------------------------------------------------------
Day 4: Stacks
Topics Covered
Introduction to Stacks
A stack is a linear data structure that follows the Last In, First Out (LIFO) principle. It's like a
collection of elements with two main operations: Push, which adds an element to the
collection, and Pop, which removes the most recently added element.
In-depth Explanation
Introduction to Stacks
- Properties: LIFO principle, dynamic size.
- Pop: Removes and returns the top element from the stack.
Code Snippet:
class Stack:
def __init__(self):
self.stack = []
def push(self, item):
self.stack.append(item)
Explanation: The `Stack` class uses an array (list in Python) to implement the stack. The `push`
method appends an item to the end of the list, effectively pushing it onto the stack.
Code Snippet:
class Node:
def __init__(self, data):
self.data = data
self.next = None
class Stack:
def __init__(self):
self.top = None
def push(self, data):
new_node = Node(data)
new_node.next = self.top
self.top = new_node
Explanation: The `Stack` class uses a linked list to implement the stack. The `push` method
creates a new node and adjusts the `top` pointer to add the new node to the stack.
Day 5: Queues
Topics Covered
Introduction to Queues
A queue is a linear data structure that follows the First In, First Out (FIFO) principle. Unlike a
stack, a queue adds elements to the end and removes them from the beginning, ensuring that
the first element added is also the first to be removed.
Queue Operations: Enqueue, Dequeue, Front
This section covers the basic operations associated with a queue—Enqueue, Dequeue, and
Front. Enqueue adds an element to the end of the queue, Dequeue removes the element from
the front, and Front allows you to view the front element without removing it.
In-depth Explanation
Introduction to Queues
• Properties: FIFO principle, dynamic size.
• Applications: Scheduling algorithms, maintaining a playlist, and in algorithms where
order needs to be maintained.
Exercises
Code Snippet:
class Queue:
def __init__(self):
self.queue = []
def enqueue(self, item):
self.queue.append(item)
Explanation: The `Queue` class uses an array (Python list) to implement the queue. The
`enqueue` method appends an item to the end of the list, effectively adding it to the queue.
Code Snippet:
import heapq
class PriorityQueue:
def __init__(self):
self.queue = []
def enqueue(self, item, priority):
heapq.heappush(self.queue, (priority, item))
Explanation: The `PriorityQueue` class uses a heap to implement a queue where elements can
have priorities. The `enqueue` method adds an item along with its priority to the heap.
-------------------------------------------------------------------------------------------------------------------------------
Day 6: Trees
Topics Covered
Binary Trees
A binary tree is a tree data structure in which each node has at most two children, commonly
referred to as the "left" and "right" child. This section provides an overview of binary trees and
how they work.
In-depth Explanation
Binary Trees
• Properties: Hierarchical structure, parent-child relationship.
• Operations: Insertion, deletion, traversal (In-order, Pre-order, Post-order).
• Applications: Used in algorithms like heapsort, and data structures like binary heaps.
Exercises
Code Snippet:
class Node:
def __init__(self, key):
self.left = None
self.right = None
self.val = key
Explanation: The `Node` class represents the nodes in the binary tree. Each node has a left
child, a right child, and a value.
Code Snippet:
def search(root, key):
if root is None or root.val == key:
return root
if root.val < key:
return search(root.right, key)
return search(root.left, key)
Explanation: The `search` function recursively searches for a given key in a binary search tree. It
navigates through the tree based on the value of each node compared to the key.
-------------------------------------------------------------------------------------------------------------------------------
In-depth Explanation
Exercises
Code Snippet:
class HashTable:
def __init__(self):
self.size = 10
self.table = [None] * self.size
Explanation: The `HashTable` class initializes a table of a fixed size (10 in this case). Each index
of the table will contain the values for keys that hash to that index.
Code Snippet:
def insert(self, key, value):
index = hash(key) % self.size
if self.table[index] is None:
self.table[index] = [(key, value)]
else:
self.table[index].append((key, value))
Explanation: The `insert` method calculates the index using the hash of the key and adds the
key-value pair to that index. If the index already contains other key-value pairs, it appends the
new pair to the list, effectively using chaining to resolve collisions.
-------------------------------------------------------------------------------------------------------------------------------
Day 8: Graphs
Topics Covered
Directed Graphs
A directed graph, also called a digraph, is a set of vertices and a collection of directed edges.
Each edge has a direction, meaning it goes from one vertex to another, not the other way
around.
Undirected Graphs
In an undirected graph, edges have no direction. That is, if an edge goes from vertex A to vertex
B, it also goes from vertex B to vertex A.
In-depth Explanation
Directed Graphs
• Properties: Vertices and directed edges.
• Operations: Insertion of vertices and edges, traversal (DFS, BFS).
• Applications: Used in network routing, social networks, and many other scenarios.
Undirected Graphs
• Properties: Vertices and undirected edges.
• Operations: Insertion of vertices and edges, traversal (DFS, BFS).
• Applications: Used in network design, pathfinding algorithms, and many more.
Exercises
Code Snippet:
class Graph:
def __init__(self):
self.graph = {}
def add_edge(self, u, v):
if u not in self.graph:
self.graph[u] = []
self.graph[u].append(v)
Explanation: The `Graph` class uses a dictionary to implement an adjacency list. The `add_edge`
method adds an edge between vertices `u` and `v`.
2. Implement a Graph using an Adjacency Matrix in Python
Objective: To understand the structure and basic operations of a graph using an adjacency
matrix.
Code Snippet:
class Graph:
def __init__(self, vertices):
self.V = vertices
self.graph = [[0 for _ in range(vertices)] for _ in
range(vertices)]
def add_edge(self, u, v):
self.graph[u][v] = 1
self.graph[v][u] = 1
Explanation: The `Graph` class uses a 2D array to implement an adjacency matrix. The
`add_edge` method sets the value at `graph[u][v]` and `graph[v][u]` to 1, indicating an edge.
-------------------------------------------------------------------------------------------------------------------------------
Memoization
Memoization is an optimization technique used to speed up programs by storing the results of
expensive function calls and returning the cached result when the same inputs occur again.
Tabulation
Tabulation is the opposite of the top-down approach and solves the problem in a bottom-up
fashion. It is about solving all the sub-problems first and using their solutions to build-on and
arrive at solutions to bigger sub-problems.
In-depth Explanation
Memoization
• Properties: Caching of results, top-down approach.
• Applications: Used in various algorithms like Fibonacci series, coin change problem, and
more.
Tabulation
• Properties: Solve and store results for all possible small sub-problems, bottom-up
approach.
• Applications: Used in optimization problems like the knapsack problem, shortest paths
in a graph, etc.
Exercises
Code Snippet:
def fib(n, memo={}):
if n in memo:
return memo[n]
if n <= 2:
return 1
memo[n] = fib(n-1, memo) + fib(n-2, memo)
return memo[n]
Explanation: The `fib` function uses a dictionary called `memo` to store Fibonacci numbers that
are already calculated. This avoids redundant calculations and improves the function's
efficiency.
2. Write a Python program for the Coin Change problem using Tabulation
Objective: To understand the concept of tabulation in dynamic programming.
Code Snippet:
def coinChange(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for coin in coins:
for x in range(coin, amount + 1):
dp[x] = min(dp[x], dp[x - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
Explanation: The `coinChange` function uses an array `dp` to store the minimum number of
coins needed to make up amounts from 0 to the target amount. This is a bottom-up approach
that fills in `dp` using smaller sub-problems to solve larger ones.
-------------------------------------------------------------------------------------------------------------------------------
Day 10: Sorting Algorithms
Topics Covered
Bubble Sort
Bubble Sort is a simple sorting algorithm that repeatedly steps through the list, compares
adjacent elements, and swaps them if they are in the wrong order.
Quick Sort
Quick Sort is an efficient, in-place sorting algorithm that uses a divide-and-conquer strategy to
sort elements.
In-depth Explanation
Bubble Sort
• Properties: In-place, stable sort.
• Operations: Comparisons and swaps.
• Applications: Suitable for small datasets and educational purposes.
Quick Sort
• Properties: In-place, unstable sort.
• Operations: Partitioning, recursive sorting.
• Applications: Used in various in-memory sorts and has numerous practical applications.
Exercises
Code Snippet:
def bubble_sort(arr):
for i in range(len(arr)):
for j in range(0, len(arr)-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
Explanation: The `bubble_sort` function iterates through the array multiple times, swapping
adjacent elements if they are in the wrong order, eventually sorting the array.
Explanation: The `quick_sort` function selects a 'pivot' element and partitions the array into
three parts: elements less than the pivot, elements equal to the pivot, and elements greater
than the pivot. It then recursively sorts the 'left' and 'right' partitions.
-------------------------------------------------------------------------------------------------------------------------------
Linear Search
Linear search is the simplest searching algorithm that searches for an element within a list in a
sequential manner, one item at a time, without jumping to any item.
Binary Search
Binary search is a fast search algorithm that finds the position of a target value within a sorted
list. It compares the target value to the middle element of the list and eliminates half of the
search interval based on the comparison.
In-depth Explanation
Linear Search
• Properties: Sequential search, no need for sorted data.
• Operations: Element-by-element comparison.
• Applications: Suitable for small and unsorted datasets.
Binary Search
• Properties: Requires sorted data, divides search interval in half at each step.
• Operations: Midpoint calculation, interval division.
• Applications: Used in searching algorithms where computational complexity needs to be
reduced.
Exercises
Code Snippet:
def linear_search(arr, x):
for i in range(len(arr)):
if arr[i] == x:
return i
return -1
Explanation: The `linear_search` function scans each element of the list one by one and returns
the index of the target element if found, otherwise returns -1.
Code Snippet:
def binary_search(arr, x):
low, high = 0, len(arr) - 1
while low <= high:
mid = (high + low) // 2
if arr[mid] < x:
low = mid + 1
elif arr[mid] > x:
high = mid - 1
else:
return mid
return -1
Explanation: The `binary_search` function takes a sorted array and a target element as input. It
uses two pointers, `low` and `high`, to find the middle element of the array. Depending on the
comparison with the middle element, it adjusts the `low` and `high` pointers.
-------------------------------------------------------------------------------------------------------------------------------
Understanding Recursion
Recursion is a technique in programming where a function calls itself. This section will cover the
basic mechanics of recursion and its advantages and disadvantages.
In-depth Explanation
Understanding Recursion
• Properties: Self-referential, base and recursive cases.
• Operations: Function calling itself.
• Applications: Used in algorithms like quick sort, depth-first search, and many others.
Exercises
1. Write a Python program for Factorial using Recursion
Objective: To understand the basic concept of recursion.
Code Snippet:
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n-1)
Explanation: The `factorial` function calculates the factorial of a number using recursion. It has a
base case when `n` is 0 and a recursive case for other values of `n`.
Code Snippet:
def fib_tail(n, a=0, b=1):
if n == 0:
return a
else:
return fib_tail(n-1, b, a+b)
Explanation: The `fib_tail` function calculates the Fibonacci number at position `n` using tail
recursion. It uses two additional arguments, `a` and `b`, to keep track of the two most recent
numbers in the sequence.
-------------------------------------------------------------------------------------------------------------------------------
Heaps
A heap is a specialized tree-based data structure that satisfies the heap property. This section
will introduce both min-heaps and max-heaps.
Trie
A Trie, or prefix tree, is a tree-like data structure that is used to store a dynamic set of strings.
Tries are generally used for searching text within a body of text.
In-depth Explanation
Heaps
• Properties: Binary tree for heaps, heap property maintenance.
• Operations: Heapify, insert, delete, and extract min/max.
• Applications: Used in algorithms like heap sort, priority queues, and more.
Trie
• Properties: Tree-like structure, nodes store the keys of a dataset of strings.
• Operations: Insert, search, and delete.
• Applications: Auto-complete features, spell checkers, and routing tables in networking.
Exercises
Code Snippet:
import heapq
def heapify(arr):
heapq.heapify(arr)
Explanation: The `heapify` function uses Python's built-in `heapq` library to convert an array
into a min-heap.
Code Snippet:
class TrieNode:
def __init__(self):
self.children = {}
self.is_end_of_word = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
node = self.root
for char in word:
if char not in node.children:
node.children[char] = TrieNode()
node = node.children[char]
node.is_end_of_word = True
Explanation: The `Trie` class uses a `TrieNode` to build a Trie data structure. The `insert` method
adds a word to the Trie, creating new TrieNodes as needed.
-------------------------------------------------------------------------------------------------------------------------------
Greedy Algorithms
Greedy algorithms build a solution piece by piece, always choosing the next piece that offers
the most immediate benefit or profit.
Greedy Algorithms
• Properties: Local optimum choices at each step.
• Operations: Iterative selection based on some heuristic.
• Applications: Used in problems like Kruskal's algorithm for minimum spanning tree,
Huffman coding, and more.
Exercises
Code Snippet:
def fractional_knapsack(value, weight, capacity):
ratio = [v/w for v, w in zip(value, weight)]
index = list(range(len(value)))
index.sort(key=lambda i: ratio[i], reverse=True)
max_value = 0
for i in index:
if capacity >= weight[i]:
max_value += value[i]
capacity -= weight[i]
else:
max_value += value[i] * (capacity / weight[i])
break
return max_value
Explanation: The `fractional_knapsack` function calculates the maximum value that can be
achieved within the given capacity. It sorts the items based on their value-to-weight ratio and
then picks the best items based on this sorted order.
2. Write a Python program for Merge Sort using Divide and Conquer
Objective: To understand the divide and conquer approach.
Code Snippet:
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = arr[:mid]
right = arr[mid:]
return merge(merge_sort(left), merge_sort(right))
Explanation: The `merge_sort` function sorts an array by dividing it into two halves, sorting
each half, and then merging them back together. This is a classic example of a divide and
conquer algorithm.
-------------------------------------------------------------------------------------------------------------------------------