The Data Structures
and Algorithms
Handbook
The Data Structures and Algorithms
Handbook
Data Structures and Algorithms (DSA) form the backbone of computer
science and software development. Whether you're preparing for coding
interviews, developing efficient software, or aiming to improve your
problem-solving skills, a strong grasp of DSA is essential. This eBook is
designed to take you through the foundational concepts in a structured
and beginner-friendly manner, gradually advancing to intermediate
topics.
With simplified explanations, real-life analogies, and practical coding
examples, the goal is to help you understand why and how things work—
not just memorize definitions or syntax. If you're someone who has just
begun your tech journey or is looking to solidify core skills, this eBook is
the perfect place to start and grow.
©GUVI Geeks Network Pvt. Ltd. 01
Table of Contents
Introduction..................................................................................................03
Chapter 1: Basics of Algorithms.........................................................07
Chapter 2: Basic Data Structures......................................................13
Chapter : on- inear Data Structures.........................................26
3 N L
Chapter 4: Recursion & Backtracking.............................................38
Chapter 5: Searching & Sorting Algorithms.................................43
Chapter 6: Hashing...................................................................................52
Chapter 7: Greedy Algorithms............................................................55
Chapter 8: Divide & Conquer...............................................................58
Chapter 9: Dynamic Programming (Introductory).....................61
Chapter 10: Practice Problems by Difficulty................................66
Chapter 11: DSA in Real-World Applications..............................73
Chapter 12: DSA ips & earning Strategies..............................80
T L
G
© UV I Geeks et ork Pvt td
N w . L . 02
Introduction
What is DSA?
DSA stands for Data Structures and Algorithms—two core concepts that
help us write better and smarter code
Data Structures are ways to organize and store data efficiently. Think
of them as different containers—like arrays, linked lists, stacks, or
trees—each suited for a specific kind of task.
Example: An array to store 5 student names:
["Amit", "Sara", "John", "Neha", "Raj"
Algorithms are the step-by-step procedures we use to solve problems.
For example, sorting a list, searching for a name in a database, or
finding the shortest route on Google Maps—all of these use
algorithms behind the scenes.
Example: An algorithm to search for a name in the array:
Go through each name one by one → if it matches, return it → else,
continue → if not found, return “Not Found”.
This is a Linear Search Algorithm working on an Array Data Structure.
(which you learn in the later section of this ebook)
In simple terms, DSA is about choosing the right "tool" (data structure)
and the right "method" (algorithm) to solve a problem in the best possible
way.
©GUVI Geeks Network Pvt. Ltd. 03
Importance of Learning DSA in
Tech Careers
If you want to grow in tech, DSA isn’t optional—it’s essential. Whether
you're aiming for a role in software development, data science, or product
engineering, DSA skills will always give you an edge.
Here’s why
Crucial for interviews: Most tech interviews are built around DSA
problems. It’s how companies test your logic, efficiency, and problem-
solving
Builds problem-solving mindset: DSA helps you break down complex
problems and design optimal solutions
Improves code quality: You’ll learn how to write code that’s not just
correct, but clean, fast, and scalable
Applicable across domains: Whether you're building websites,
designing apps, or working on AI, DSA plays a key role behind the
scenes
Huge Demand: You can see huge demand for DSA experts, be it for
MNCs, or startups. Not only top companies, but startups also go for
DSA experts and pay handsome salaries.
Mastering DSA is like learning how to think like a developer—and that’s a
skill that will stay with you forever.
©GUVI Geeks Network Pvt. Ltd. 04
How to Use This eBook
This eBook is designed to be your companion as you journey from DSA
beginner to confident problem solver. Whether you're just getting started
or looking to strengthen your core concepts, here’s something for you.
To make the most of it
Follow the chapters in order for a structured learning path—from
basics to intermediate topics
Don’t rush. Read, understand the concepts, and try out the examples
on your own
Practice is key! At the end of each topic, you’ll find example problems
to apply what you’ve learned
Revisit challenging sections—repetition helps concepts stick.
Tip: If you pair this eBook with regular hands-on practice on coding
platforms, your progress will be much faster and more effective.
Prerequisites (Basic Programming
Knowledge)
You don’t need to be a coding expert to start learning DSA—but having
some basic programming knowledge will help you feel more comfortable
as you go.
Here’s what you should already be familiar with
Writing simple programs in a language like Python, Java, C++, or
JavaScript
©GUVI Geeks Network Pvt. Ltd. 05
Using variables, loops, and conditional statements (if/else
Defining and calling function
Working with basic arrays or list
Performing simple input/output operations
If you’re still learning these, that’s okay! Just take some time to get the
basics down first, and then come back—you’ll get much more out of this
eBook.
©GUVI Geeks Network Pvt. Ltd. 06
Chapter 1: Basics of Algorithms
1.1 What is an Algorithm?
At its core, an algorithm is a set of clear, step-by-step instructions used
to perform a task or solve a problem. It's like a recipe in cooking—you
follow specific steps in a specific order to get the desired dish (or result).
For example
Want to make a cup of tea? Boil water → Add tea leaves → Add milk
and sugar → Strain → Serve. That’s an algorithm.
In computer science, algorithms help us solve problems using logic and
structure. Whether it’s sorting a list, finding the shortest path, or filtering
data, there’s always an algorithm behind the scenes doing the work.
Characteristics of a Good Algorith
Input: Takes one or more values to work o
Output: Produces a meaningful resul
Definiteness: Each step is clear and unambiguou
Finiteness: It must end after a finite number of step
Effectiveness: All steps should be doable using basic operations
Let’s break each of these down.
©GUVI Geeks Network Pvt. Ltd. 07
Input / Outpu
Input: An algorithm may require zero, one, or multiple inputs. These are the
values it needs to begin the process.
Example: In a search algorithm, the input might be a list and a target value
Output: The algorithm must produce at least one output—a result or
solution based on the given input.
Example: The index of the target value in the list, or “not found” if it
doesn't exist.
Definiteness
Every step in the algorithm must be precise and well-defined. There should be
no confusion about what needs to be done.
Bad instruction: “Do something with the number”
Good instruction: “Multiply the number by 2 and store the result in variable
X”
Clarity ensures the algorithm behaves the same way every time it's executed.
Finiteness
An algorithm should always come to an end after a finite number of steps. If it
runs endlessly without stopping, it’s not a valid algorithm.
Example: A loop that checks each element in a list and stops once the target is
found. If it never exits, something’s gone wrong!
©GUVI Geeks Network Pvt. Ltd. 08
Effectiveness
Every operation in the algorithm must be simple and executable. It shouldn't
rely on anything abstract or undefined.
That means
Each step can be performed using basic tools or logi
You could carry it out by hand if needed (in theory)
1.2 Time & Space Complexity
When writing code, it’s not just about solving the problem—it’s about
solving it efficiently. That’s where Time and Space Complexity come in
Time Complexity tells us how fast an algorithm runs as the input size
increases
Space Complexity tells us how much extra memory the algorithm uses
during its execution.
Imagine two sorting algorithms: one takes 5 seconds, and another takes 2
seconds for the same task. The faster one is more time-efficient. Similarly,
an algorithm that uses less memory (without slowing down) is more
space-efficient. As your inputs grow—like processing large datasets—
these differences matter a lot.
Big O Notation
To measure complexity, we use Big O Notation. It describes how the
©GUVI Geeks Network Pvt. Ltd. 09
runtime or memory grows with respect to the input size (usually denoted by n).
Big O helps you answer questions like
Will this algorithm still work fast with 1,000 or 1 million inputs
How does it scale as data grows
Some common notations
O(1) — Constant time (fastest
O(n) — Linear time
O(n²) — Quadratic time (can get slow
O(log n) — Logarithmic time (very efficient for large inputs)
You don’t have to be a math wizard—just understand the pattern of growth.
Best, Average, and Worst Case
Every algorithm behaves differently depending on the input. That’s why we often an
Best Case: The ideal scenario (usually rare
Average Case: The typical or expected behavio
Worst Case: The slowest or most resource-hungry scenario
For example, in Linear Search
Best Case: The element is at the start → O(1)
©GUVI Geeks Network Pvt. Ltd. 10
Average Case: It’s somewhere in the middle → O(n/2) → simplified to
O(n
Worst Case: It’s at the end or not found at all → O(n)
In interviews and real-world scenarios, worst-case analysis is often the
most important.
Common Time Complexities Explained (with
Examples)
Here are some of the most common time complexities you’ll come across,
along with what they really mean
O(1) – Constant Time
No matter the input size, the algorithm takes the same amount of
time.
Example: Accessing an element in an array: arr[5
O(n) – Linear Time
Time grows directly with input size.
Example: Looping through an array onc
O(log n) – Logarithmic Time
Input is reduced by half at each step.
Example: Binary Search on a sorted lis
O(n log n)
Faster than O(n²) but slower than O(n).
Example: Merge Sort, Quick Sort (average case)
©GUVI Geeks Network Pvt. Ltd. 11
O(n²) – Quadratic Time
Time grows with the square of input size—common in nested loops.
Example: Bubble Sort or checking all pairs in an array
Understanding these patterns helps you write code that not only works—but
works well.
Let’s understand it with a very short example:
def find_sum(arr):
total = 0
for num in arr:
total += num
return total
Time Complexity: O(n) — The loop runs once for each element in the
array.
Space Complexity: O(1) — Only one extra variable (total) is used,
regardless of input size.
©GUVI Geeks Network Pvt. Ltd. 12
Chapter 2: Basic Data Structures
2.1 Arrays
An array is one of the simplest and most commonly used data structures.
It stores elements of the same type in a contiguous block of memory and
allows direct access using an index.
Think of it like a row of mailboxes—each one labeled with a number
(index), and each holding a value (data). Arrays are perfect when you
know the number of elements in advance and want to quickly access or
update them.
Declaration & Initialization
To use an array, you first need to declare it and then fill it with values
In Python:
arr = [10, 20, 30, 40, 50
In C++:
int arr[5] = {10, 20, 30, 40, 50}
In Java:
int[] arr = {10, 20, 30, 40, 50};
All elements are stored next to each other in memory, and you can access any
value using its index (starting from 0).
©GUVI Geeks Network Pvt. Ltd. 13
Operations: Insertion, Deletion, Traversal
Here are the basic operations you can perform on an array
Insertion
Add an element at a specific position (manually shifting other
elements if needed)
Deletion
Remove an element and shift the rest to fill the gap
Traversal
Go through each element, usually using a loop.
Example (Python):
for i in range(len(arr)):
print(arr[i])
Arrays make traversal fast and easy—but insertion and deletion can
become slow if you’re working with large datasets and need to shift many
elements.
Example: Find Max Element
Let’s write a simple code to find the maximum value in an array:
def find_max(arr):
max_val = arr[0]
for num in arr:
if num > max_val:
max_val = num
return max_val
©GUVI Geeks Network Pvt. Ltd. 14
Time Complexity: O(n) — We check each element once
Space Complexity: O(1) — We use only one extra variable (max_val).
2.2 Strings
A string is a sequence of characters—letters, numbers, symbols—used to
represent text in any programming language. Strings are essential in
almost every program, whether you're storing a name, processing user
input, or building messages.
In most languages, strings are treated as arrays of characters, but with a
key difference: they're usually immutable, meaning they cannot be
changed once created.
Immutable Nature
In many languages like Python and Java, strings are immutable. That
means when you try to modify a string, a new string is created instead of
changing the original one.
For example (Python):
s = "hello"
s = s + " world"
print(s) # Output: "hello world"
The original "hello" isn’t changed; instead, a new string "hello world" is
created and assigned back to s.
©GUVI Geeks Network Pvt. Ltd. 15
Common Operations (Concatenation,
Substrings, Reverse)
Here are a few common operations you’ll perform on strings
Concatenation – Joining two strings:
"Hello" + " " + "World" # "Hello World
Substrings – Extracting a portion of the string:
s = "Scaler"
print(s[1:4]) # "cal"
c.Reversing a string:
s = "hello"
print(s[::-1]) # "olleh"
Strings may look simple, but they are incredibly powerful and used in a
wide range of problems—from pattern matching to encryption.
Example: Palindrome Checker
A palindrome is a word or phrase that reads the same backward as forward
(e.g., madam, racecar). Here's a simple Python function to check for one:
def is_palindrome(s):
return s == s[::-1]
Time Complexity: O(n) — We compare all characters once.
©GUVI Geeks Network Pvt. Ltd. 16
Space Complexity: O(1) — No extra space (ignoring the reversed string
stored in memory temporarily).
2.3 Linked Lists
A Linked List is a linear data structure where each element (called a
node) contains two things
The actual dat
A reference (or pointer) to the next node in the sequence
Unlike arrays, linked lists do not store elements in contiguous memory
locations. This makes them great for dynamic memory usage—especially
when frequent insertions or deletions are involved.
Think of a linked list as a chain of people holding hands—each person
knows who’s next but not necessarily where they are in space.
Singly vs Doubly vs Circula
Singly Linked List
Each node holds data and a pointer to the next node. Traversal is one-
way only.
Example: 10 → 20 → 30 → nul
Doubly Linked List
Each node holds data, a pointer to the next node, and another to the
previous. Traversal is bidirectional.
Example: null ← 10 ⇄ 20 ⇄ 30 → null
©GUVI Geeks Network Pvt. Ltd. 17
Circular Linked List
The last node connects back to the first, forming a loop. Can be singly
or doubly linked.
Example: 10 → 20 → 30 → (back to 10)
Each type is chosen based on how you want to navigate and manipulate
the data.
Operations: Insert, Delete, Travers
Insert: Add a node at the start, end, or any position by adjusting the
pointers
Delete: Remove a node by reconnecting the previous node to the next
one
Traverse: Move through each node to read or process data.
Example: Traverse and print a singly linked list:
def traverse(head):
current = head
while current:
print(current.data, end=" → ")
current = current.next
If your list is 10 → 20 → 30 → null, this will print:
10 → 20 → 30 →
Example: Reverse a Linked List
Reversing a linked list means flipping the direction of all the pointers:
def reverse_linked_list(head):
©GUVI Geeks Network Pvt. Ltd. 18
prev = None
current = head
while current:
next_node = current.next
current.next = prev
prev = current
current = next_node
return prev
Before: 10 → 20 → 30 → null
After: 30 → 20 → 10 → nul
Time Complexity: O(n) – Each node is visited onc
Space Complexity: O(1) – Uses only a few variables
2.4 Stacks
A stack is a linear data structure that follows the LIFO principle—Last In,
First Out. This means the last item added to the stack is the first one to
be removed, just like a stack of plates: you add to the top, and you
remove from the top.
Stacks are simple but incredibly useful in programming, especially when
dealing with tasks that need to be "undone" or rolled back.
LIFO Principle
LIFO (Last In, First Out) means the last element inserted is the first one to
come out.
©GUVI Geeks Network Pvt. Ltd. 19
Think of
A stack of book
Browser histor
Undo feature in an editor
Add (push) and remove (pop) operations only happen at the top of the
stack. You can also peek to check the top element without removing it.
Example (Python):
stack = []
stack.append(10) # push
stack.append(20)
print(stack.pop()) # output: 20 (last in, first out)
Applications (Expression Evaluation, Undo
Operations)
Stacks are widely used in real-world problems. Some key applications include
Expression Evaluation
Solving mathematical expressions like ((a + b) * c) using stacks for
parentheses and operators
Undo/Redo Operations
Text editors use stacks to keep track of actions. Pressing “Undo” simply
pops the last operation.
©GUVI Geeks Network Pvt. Ltd. 20
Function Call Stack
Programming languages use a stack to keep track of function calls and
returns
Backtracking
In problems like maze solving or recursive puzzles (e.g., Sudoku), stacks
help reverse steps
Implementation using Array & Linked List
You can implement a stack using:
Array (or Python list):
stack = []
stack.append(1) # push
stack.pop() # pop
Linked List:
Create a node class where each node points to the next. New elements are
added and removed from the head of the list.
class Node:
def __init__(self, data):
self.data = data
self.next = None
class Stack:
def __init__(self):
self.top = None
©GUVI Geeks Network Pvt. Ltd. 21
def push(self, data):
node = Node(data)
node.next = self.top
self.top = node
def pop(self):
if self.top:
value = self.top.data
self.top = self.top.next
return value
Both approaches are valid—arrays are simpler, but linked lists are more
flexible for dynamic memory.
Example: Balanced Parentheses
One common stack problem is checking if parentheses are balanced in an
expression.
def is_balanced(expr):
stack = []
for char in expr:
if char == '(':
stack.append(char)
elif char == ')':
if not stack:
return False
stack.pop()
return not stac
Input: "((a + b) * c)" → Output: Tru
Input: "((a + b)" → Output: False
©GUVI Geeks Network Pvt. Ltd. 22
Time Complexity: O(n) — We check each character onc
Space Complexity: O(n) — In the worst case, all opening brackets go
into the stack
2.5 Queues
A queue is a linear data structure that works on the FIFO principle —
First In, First Out. The element that is inserted first is the one that gets
removed first. Think of a real-life queue at a movie theatre: the first
person to join is the first to be served.
Just like stacks, queues have restricted access — elements are inserted
at the rear and removed from the front.
FIFO Principle
FIFO (First In, First Out) means the earliest added element leaves the
queue first.
Example:
from collections import deque
queue = deque()
queue.append(10) # enqueue
queue.append(20)
print(queue.popleft()) # Output: 10
Use-cases of FIFO include
Printing jobs in a printer queu
Customer support call lines
©GUVI Geeks Network Pvt. Ltd. 23
Task scheduling in operating systems
Types: Simple, Circular, Deque, Priority Queue
There are several variations of queues, each with its own purpose
Simple Queue
Basic queue where insertion is at the rear and deletion is from the
front
Circular Queue
The last position connects back to the first, forming a circle. It
optimizes memory usage by reusing emptied spaces
Deque (Double-Ended Queue)
Allows insertion and deletion from both front and rear. Useful in
sliding window problems
Priority Queue
Each element is assigned a priority. Higher priority elements are
served first, regardless of arrival order.
Applications (Task Scheduling, OS Queues)
Queues are widely used in
CPU/Process Scheduling
Operating systems use queues to manage jobs/tasks
Breadth-First Search (BFS)
In graphs and trees, BFS uses queues to explore levels layer by layer.
©GUVI Geeks Network Pvt. Ltd. 24
Message Queues
Backend systems (like RabbitMQ or Kafka) use queues to handle data
flow between services
Print Queues / Customer Requests
Sequentially handling incoming jobs or requests.
Example: Queue using Two Stacks
You can implement a queue using two stacks to simulate FIFO behavior
using LIFO structures.
class QueueUsingStacks:
def __init__(self):
self.stack1 = []
self.stack2 = []
def enqueue(self, x):
self.stack1.append(x)
def dequeue(self):
if not self.stack2:
while self.stack1:
self.stack2.append(self.stack1.pop())
if self.stack2:
return self.stack2.pop()
return Non
Enqueue adds elements to stack
Dequeue transfers elements to stack2 only when needed (reversing the
order)
©GUVI Geeks Network Pvt. Ltd. 25
Chapter 3: Non-Linear Data Structures
3.1 Trees
A Tree is a hierarchical data structure made up of nodes connected by
edges. Unlike linear structures (like arrays or linked lists), trees allow
branching—making them ideal for representing structured data like file
systems, family trees, or decision models.
Every tree starts from a special node called the root, and branches out
into children, forming a parent-child relationship.
Terminologies (Root, Leaf, Node, Depth)
Understanding basic tree vocabulary helps in mastering tree operations
Root: The topmost node in the tre
Node: Each element in the tre
Leaf: A node with no childre
Parent/Child: A node connected to other nodes below i
Depth: The level of a node from the roo
Height: The longest path from the root to a leaf
Example:
In a family tree, the root could be a grandparent. Children, grandchildren,
and so on represent child nodes at increasing depths.
©GUVI Geeks Network Pvt. Ltd. 26
Types: Binary Tree, BST, AVL, Heap
Trees come in different forms based on their structure and constraints
Binary Tree: Each node has at most two children (left and right)
Binary Search Tree (BST): A binary tree where the left child < parent <
right child. Helps in fast lookup
AVL Tree: A self-balancing BST. Maintains balance to ensure operations
stay efficient (O(log n))
Heap: A special tree where the parent is either greater (Max-Heap) or
smaller (Min-Heap) than its children. Used in priority queues.
Traversals: Inorder, Preorder, Postorder
Tree traversal means visiting all nodes in a specific order. The three
common types are
Inorder (Left → Root → Right): Gives sorted order in BS
Preorder (Root → Left → Right): Useful for copying the tre
Postorder (Left → Right → Root): Useful for deleting the tree
Example (Inorder Traversal of Binary Tree):
def inorder(root):
if root:
inorder(root.left)
print(root.data)
inorder(root.right)
©GUVI Geeks Network Pvt. Ltd. 27
Example: Height of Binary Tree
The height of a binary tree is the number of edges in the longest path from
the root to a leaf.
class Node:
def __init__(self, data):
self.data = data
self.left = self.right = None
def height(root):
if not root:
return -1
return 1 + max(height(root.left), height(root.right)
A single node has a height of 0
An empty tree has a height of -1
Time Complexity: O(n) — We visit each node once.
3.2 Binary Search Trees (BST)
A Binary Search Tree (BST) is a special type of binary tree where every
node follows the rule:
Left subtree < Node < Right subtree
This structure ensures that searching, insertion, and deletion operations
can be done efficiently—often in O(log n) time for balanced trees.
©GUVI Geeks Network Pvt. Ltd. 28
Insertion, Deletion, Searchin
Insertion: Always insert a new value in the left or right subtree
depending on whether it's smaller or greater than the current node
Searching: Start at the root and move left or right based on comparison
until you find the value or reach a None
Deletion: Trickiest part. Three cases
Node has no children — simply remove it
Node has one child — link the child with the parent
Node has two children — find in-order successor (smallest in right
subtree), replace, then delete.
Example - Search in BST
def search(root, key):
if not root or root.data == key:
return root
if key < root.data:
return search(root.left, key)
return search(root.right, key)
Applications
BSTs are widely used due to their ability to keep data sorted
Databases and File Systems — For fast retrieval
©GUVI Geeks Network Pvt. Ltd. 29
Autocomplete Suggestions — Based on sorted word searche
Set and Map Implementations — Many programming languages use BST
internall
Range Queries — To quickly find data within a given rang
Memory Management — For efficient allocation and freeing of memory
blocks
Example: Check if BST is Balanced
A balanced BST ensures the depth of left and right subtrees of every node
differs by at most 1. This keeps operations efficient.
Here's a simple way to check:
def is_balanced(root):
def check(node):
if not node:
return 0
left = check(node.left)
right = check(node.right)
if left == -1 or right == -1 or abs(left - right) > 1:
return -1
return 1 + max(left, right)
return check(root) != -1
Returns True if the BST is height-balanced.
Helps in determining whether to convert to a self-balancing BST like AVL or
Red-Black Tree.
©GUVI Geeks Network Pvt. Ltd. 30
3.3 Heaps
A Heap is a special kind of complete binary tree used to maintain a
specific order among elements. It follows a heap property
In a Max Heap, every parent node is greater than or equal to its
children
In a Min Heap, every parent node is less than or equal to its children.
Heaps are typically implemented using arrays and are mainly used in
priority-based systems.
Max Heap vs Min Hea
Max Heap: The largest element is at the root. Useful when you want
quick access to the maximum value
Min Heap: The smallest element is at the root. Useful in algorithms
where the smallest element needs priority.
Visual Example
Max Heap
Min Heap
50
10
/ \
/ \
30 20
20 30
/ \
/ \
15 10 50 40
©GUVI Geeks Network Pvt. Ltd. 31
Heapify Process
Heapify is the process of converting an array into a heap (either max or min
heap). It's used to maintain the heap property after insertion or deletion
Start from the bottom non-leaf nodes and move upward
At each node, compare with its children and swap if needed to maintain
the heap condition.
Example – Max Heapify
def heapify(arr, n, i):
largest = i
left = 2*i + 1
right = 2*i + 2
if left < n and arr[left] > arr[largest]:
largest = left
if right < n and arr[right] > arr[largest]:
largest = right
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
Applications: Priority Queue
Heaps are the backbone of priority queues—a special type of queue where
the element with the highest (or lowest) priority is served first.
©GUVI Geeks Network Pvt. Ltd. 32
Real-world use
CPU Scheduling (OS tasks
Dijkstra's Algorithm (shortest path
Event Management System
Job Scheduling in Print Queues
Example: Heap Sort
Heap Sort is a comparison-based sorting technique that uses a heap to sort
elements in ascending or descending order.
Steps
Build a max heap
Swap root (max element) with the last element
Reduce heap size and heapify again
Repeat until sorted.
Python Example:
def heap_sort(arr):
n = len(arr)
# Step 1: Build max heap
for i in range(n//2 - 1, -1, -1):
heapify(arr, n, i)
©GUVI Geeks Network Pvt. Ltd. 33
# Step 2: Extract elements
for i in range(n-1, 0, -1):
arr[0], arr[i] = arr[i], arr[0] # Swap
heapify(arr, i, 0)
return arr
Time Complexity: O(n log n)
Space Complexity: O(1) – in-place sorting
3.4 Graphs
A Graph is a non-linear data structure consisting of nodes (vertices) and
edges that connect them. Unlike trees, graphs can have cycles, and each
node can connect to multiple nodes in any direction.
Graphs are used in real-world systems like social networks, GPS
navigation, and recommendation engines.
Representations: Adjacency List vs Matrix
Graphs can be represented in two common ways
Adjacency List: Each node stores a list of its connected nodes
Space-efficient for sparse graph
Easier to traverse neighbor
Example:
graph = {
©GUVI Geeks Network Pvt. Ltd. 34
'A': ['B', 'C'],
'B': ['A', 'D'],
'C': ['A'],
'D': ['B']
Adjacency Matrix: A 2D array where matrix[i][j] = 1 if there's an edge
from i to j
Quick edge lookup
More space-consuming (O(n²))
Types: Directed, Undirected, Weighte
Directed Graph (Digraph): Edges have direction, like a one-way road
Undirected Graph: Edges go both ways
Weighted Graph: Each edge carries a "cost" or "distance" value.
Example:
A flight route system is a directed weighted graph, where each flight (edge)
has a cost and direction (from source to destination).
Traversals: BFS, DFS
To explore or search a graph, we use
BFS (Breadth-First Search)
Visits all neighbors first before going deeper
©GUVI Geeks Network Pvt. Ltd. 35
Uses a queu
Great for finding shortest paths in unweighted graphs
DFS Example (recursive)
def dfs(graph, node, visited):
if node not in visited:
visited.add(node)
for neighbor in graph[node]:
dfs(graph, neighbor, visited)
Example: Detect Cycle in a Graph
Cycle detection helps prevent infinite loops and is critical in scheduling
problems.
Using DFS in Directed Graph:
def has_cycle(graph):
visited = set()
rec_stack = set()
def dfs(node):
visited.add(node)
rec_stack.add(node)
©GUVI Geeks Network Pvt. Ltd. 36
for neighbor in graph[node]:
if neighbor not in visited:
if dfs(neighbor):
return True
elif neighbor in rec_stack:
return True
rec_stack.remove(node)
return False
for node in graph:
if node not in visited:
if dfs(node):
return True
return Fals
Returns True if a cycle exist
Used in deadlock detection, course scheduling, and dependency
resolution
©GUVI Geeks Network Pvt. Ltd. 37
Chapter 4: Recursion & Backtracking
4.1 Introduction to Recursion
Recursion is a programming technique where a function calls itself to
solve a smaller part of a bigger problem. Instead of using loops, recursion
breaks a problem down into smaller, manageable sub-problems that
follow the same logic.
Think of recursion like Russian nesting dolls—each doll contains a smaller
version of itself, until you reach the smallest one.
Base Case & Recursive Case
Every recursive function has two essential parts
Base Case: The condition that stops the recursion. Without this, the
function would keep calling itself infinitely
Recursive Case: The part where the function calls itself with a
smaller input, gradually moving toward the base case.
Example: Factorial of n (n!)
def factorial(n):
if n == 0: # Base Case
return 1
return n * factorial(n - 1) # Recursive Case
Calling factorial(3) breaks down like
3 * factorial(2
2 * factorial(1)
©GUVI Geeks Network Pvt. Ltd. 38
1 * factorial(0
factorial(0) returns 1, and the results build back up to give 6.
Stack Memory Use
Recursion internally uses the call stack. Every time a function calls itself, the
current state is saved on the stack until the base case is reached.
As the recursive calls return, each saved state is popped off the stack,
completing the previous operations.
Note: Too many recursive calls can lead to stack overflow errors—this
happens if there’s no proper base case or the recursion goes too deep
Example: Factorial, Fibonacci
Factorial (as shown above) is a classic recursive problem.
Now, let’s look at the Fibonacci Sequence, where
F(0) =
F(1) =
F(n) = F(n-1) + F(n-2)
def fibonacci(n):
if n <= 1: # Base Case
return n
return fibonacci(n-1) + fibonacci(n-2) # Recursive Case
fibonacci(4) returns
fibonacci(3) + fibonacci(2
Which further breaks into fibonacci(2) + fibonacci(1)... and so on
While elegant, this version of Fibonacci is inefficient for large n due to
repeated calls. You can improve it using memoization or dynamic
programming later.
©GUVI Geeks Network Pvt. Ltd. 39
4.2 Backtracking
Backtracking is a problem-solving algorithmic technique used for
exploring all possible solutions by building a solution step-by-step and
abandoning a path (backtracking) as soon as it determines that this path
won’t lead to the desired result.
Think of it like navigating a maze—at each turn, you choose a path, and if
it leads to a dead end, you go back and try another until you reach the
exit. This is the essence of backtracking: Try → Fail → Backtrack → Try
Again.
Backtracking is commonly used in problems where we need to make a
series of decisions and are looking for all possible combinations,
permutations, or valid configurations
Concept: Try, Fail, Backtrack
The process of backtracking usually involves
Trying a step (choice/decision
Checking if it leads to a valid solutio
If yes, proceed with the next ste
If not, undo the step (backtrack) and try a different option
It’s often implemented using recursion and is efficient because it avoids
checking all paths unnecessarily by abandoning ones that clearly don’t
work.
©GUVI Geeks Network Pvt. Ltd. 40
Classic Problems: N-Queens, Sudoku Solver
Some well-known problems solved using backtracking include
N-Queens Problem: Placing N queens on an N×N chessboard
such that no two queens threaten each other (i.e., no two are in
the same row, column, or diagonal)
Sudoku Solver: Filling empty cells in a Sudoku grid while
following the game's constraints
Permutations/Combinations Problem
Crossword Fillers / Word Search
These problems have multiple possible paths, and backtracking .
efficiently finds valid configurations
Example: Subset Sum Problem
The Subset Sum Problem asks:
"Given a set of integers, is there a subset whose sum equals a given
number?"
We try including or excluding each element and recursively explore all
combinat
def is_subset_sum(arr, n, target):
if target == 0:
return True
if n == 0:
return False
©GUVI Geeks Network Pvt. Ltd. 41
if arr[n-1] > target:
return is_subset_sum(arr, n-1, target)
# Try including OR excluding the current element
return (is_subset_sum(arr, n-1, target) or
is_subset_sum(arr, n-1, target - arr[n-1]))
# Example:
arr = [3, 34, 4, 12, 5, 2]
target = 9
print(is_subset_sum(arr, len(arr), target)) # Output: True
Explanatio
We either include the current element in the subset or exclude it
If any path leads to the target sum, we return True
If all paths are exhausted and none match the target, we return False.
Backtracking is a powerful tool when you need to generate all possible
answers, find constraints-based solutions, or explore choices with
rollback options. Though recursive in nature, it can be optimized using
techniques like pruning and memoization.
©GUVI Geeks Network Pvt. Ltd. 42
Chapter 5: Searching & Sorting
Algorithms
5.1 Searching Techniques
Searching is a fundamental concept in computer science that involves
finding the location or existence of an element within a data structure
like an array, list, or tree. Efficient searching helps reduce program
execution time and improves performance significantly.
There are various searching algorithms, but two of the most widely used
for arrays are Linear Search and Binary Search. The choice depends on
whether the data is sorted or not.
Linear Search
Linear Search is the most straightforward searching technique. It checks
each element in the list one by one until the target is found or the list
ends.
When to use: When the list is unsorted or small.
def linear_search(arr, target):
for i in range(len(arr)):
if arr[i] == target:
return i # Found at index i
return -1 # Not found
# Example:
arr = [7, 2, 5, 9, 1]
print(linear_search(arr, 9)) # Output: 3
©GUVI Geeks Network Pvt. Ltd. 43
Time Complexity: O(n
Space Complexity: O(1)
Binary Search (Recursive and Iterative)
Binary Search works on sorted arrays and is much faster than linear
search. It repeatedly divides the search interval in half:
If the target is less than the middle element, search the left half
If greater, search the right half
If equal, you've found the element!
When to use: When the array is sorted.
Iterative Version:
def binary_search_iterative(arr, target):
low, high = 0, len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
©GUVI Geeks Network Pvt. Ltd. 44
Recursive Version:
def binary_search_recursive(arr, low, high, target):
if low > high:
return -1
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
return binary_search_recursive(arr, mid + 1, high, target)
else:
return binary_search_recursive(arr, low, mid - 1, target
Time Complexity: O(log n
Space Complexity: O(1) for iterative, O(log n) for recursive (due to call
stack)
Example: Binary Search in Rotated Array
A rotated sorted array is a sorted array that has been "rotated" at some
pivot. For example, [6, 7, 8, 1, 2, 3, 4]. We need a modified binary search to
handle this.
def search_rotated(arr, target):
low, high = 0, len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid
if arr[low] <= arr[mid]: # Left half is sorted
if arr[low] <= target < arr[mid]:
high = mid - 1
©GUVI Geeks Network Pvt. Ltd. 45
else:
low = mid + 1
else: # Right half is sorted
if arr[mid] < target <= arr[high]:
low = mid + 1
else:
high = mid - 1
return -1
# Example:
arr = [6, 7, 8, 1, 2, 3, 4]
print(search_rotated(arr, 3)) # Output: 5
This is a practical real-world example where regular binary search doesn’t
work, and a modified version is needed.
5.2 Sorting Techniques
Sorting is the process of arranging elements in a particular order—most
commonly in ascending or descending order. Efficient sorting is crucial in
optimizing other operations like searching, merging, and even visual
presentation of data.
Let’s walk through some of the most common sorting algorithms, starting
from the basics and moving to more efficient ones.
Bubble, Insertion, Selection
1. Bubble Sort:
Compares adjacent elements and swaps them if they are in the wrong
order. Repeats this until the list is sorted.
©GUVI Geeks Network Pvt. Ltd. 46
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
Time: O(n²), Space: O(1)
2. Insertion Sort:
Builds the final sorted array one item at a time by comparing and
inserting elements in the correct position.
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and key < arr[j]:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr
Time: O(n²), Space: O(1)
3. Selection Sort:
Selects the minimum element and places it at the beginning of the
array.
def selection_sort(arr):
for i in range(len(arr)):
©GUVI Geeks Network Pvt. Ltd. 47
min_idx = i
for j in range(i+1, len(arr)):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
return arr
Time: O(n²), Space: O(1)
Merge Sort, Quick Sort
These are Divide and Conquer algorithms, more efficient for large
datasets.
1. Merge Sort:
Splits the array into halves, sorts them recursively, and then merges.
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr)//2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
result.append(left[i] if left[i] < right[j] else right[j])
i += left[i] < right[j]
j += left[i] >= right[j]
©GUVI Geeks Network Pvt. Ltd. 48
result.extend(left[i:])
result.extend(right[j:])
return result
Time: O(n log n), Space: O(n)
2. Quick Sort:
Picks a pivot and partitions the array such that left of pivot is less and right
is greater, then recursively sorts each side.
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[0]
less = [x for x in arr[1:] if x <= pivot]
greater = [x for x in arr[1:] if x > pivot]
return quick_sort(less) + [pivot] + quick_sort(greater)
Time: Avg O(n log n), Worst O(n²), Space: O(log n)
Counting & Radix Sort (Introductory level)
1. Counting Sort:
Only works on integers within a known range. It counts occurrences and
reconstructs the sorted array.
def counting_sort(arr):
count = [0] * (max(arr) + 1)
for num in arr:
count[num] += 1
sorted_arr = []
for i in range(len(count)):
sorted_arr.extend([i] * count[i])
return sorted_arr
©GUVI Geeks Network Pvt. Ltd. 49
Time: O(n + k), where k is the range of number
Limitation: Only for non-negative integers
Radix Sort:
Sorts integers by processing digits from least significant to most
significant. Works best when number of digits (d) is small
Time: O(nk), where k = number of digit
Used when dealing with large datasets with limited-length integers
Comparison Table for Time Complexities
Algorithm Time (Best) Time (Avg) Time (Worst) Space
Bubble Sort O(n) O(n²) O(n²) O(1)
Insertion Sort O(n) O(n²) O(n²) O(1)
Selection Sort O(n²) O(n²) O(n²) O(1)
Merge Sort O(n log n) O(n log n) O(n log n) O(n)
Quick Sort O(n log n) O(n log n) O(n²) O(log n)
Counting Sort O(n + k) O(n + k) O(n + k) O(k)
Radix Sort O(nk) O(nk) O(nk) O(n + k)
©GUVI Geeks Network Pvt. Ltd. 50
Example: Quick Sort Algorithm
Let’s sort this array: [4, 2, 7, 1, 9
Pivot:
Less: [2, 1
Greater: [7, 9
Recurse → [1, 2] + [4] + [7, 9] → Output: [1, 2, 4, 7, 9]
It’s one of the most used algorithms in real-world applications like sorting
in-built data structures (sort() in many languages use it under the hood).
©GUVI Geeks Network Pvt. Ltd. 51
Chapter 6: Hashing
Hash Tables & Hash Maps
A Hash Table (or Hash Map) is a data structure that stores data in key-value
pairs, making retrieval incredibly fast—typically in constant time O(1). Think of
it like a digital dictionary: you search using a key and instantly get the value.
The key is passed through a hash function to generate an index where the
value is stored. But sometimes, two keys might produce the same index—this
is called a collision, and handling it efficiently is crucial.
Collision Handling: Chaining & Open Addressing
1. Chaining:
If multiple keys map to the same index, we store them in a linked list (or
another data structure) at that index.
# Example using Python dict (built-in hash table)
hash_table = {}
hash_table["apple"] = 1
hash_table["banana"] = 2
If "apple" and "banana" were to hash to the same index internally, Python
would handle it by chaining them in a list-like structure behind the scenes.
2. Open Addressing:
Instead of storing multiple values in the same index, we probe (search) for the
next empty slot. Techniques include:
©GUVI Geeks Network Pvt. Ltd. 52
Linear Probing: Move forward one slot at a time
Quadratic Probing: Move in increasing square steps
Double Hashing: Use a second hash function to determine steps.
Use Cases: Dictionary, Frequency Count
Hash tables are used extensively across tech and real-world software
Dictionaries/Maps: Most programming languages offer built-in hash
maps (dict in Python, Map in JavaScript, etc.
Frequency Counter: Count how often elements appear in a list
Caching: Store previously computed results for faster access
(memoization)
Lookups: Quickly check presence of an item in a dataset (like a spell
checker)
Databases: Indexing data for rapid querying.
Example: Find First Non-Repeating Character
Let’s say we have a string and want to find the first character that doesn’t
repeat.
def first_non_repeating_char(s):
freq = {}
for char in s:
freq[char] = freq.get(char, 0) + 1
©GUVI Geeks Network Pvt. Ltd. 53
for char in s:
if freq[char] == 1:
return char
return None
print(first_non_repeating_char("aabbcdeff")) # Output: c
Explanatio
First, we build a frequency hash map where each character is a key
and its count is the value
Then, we check characters in the original order to find the first one
with a count of 1.
Time Complexity: O(n)
Space Complexity: O(n)
©GUVI Geeks Network Pvt. Ltd. 54
Chapter 7: Greedy Algorithms
A Greedy Algorithm is an approach for solving problems by making the best
possible choice at each step, hoping that these local optimal choices will lead
to a globally optimal solution.
Greedy algorithms are typically used when
Making decisions piece-by-piece is possible
Once a choice is made, it cannot be changed
The problem has an optimal substructure and greedy-choice property.
This strategy works brilliantly for some problems, but not all—so knowing
when to use it is key!
Concept of Local vs Global Optim
Local Optimum: The best solution at a single step or stage
Global Optimum: The best overall solution to the entire problem.
Greedy algorithms always pick the local optimum with the hope that these
decisions will add up to a global optimum. However, this doesn’t work for all
problems, so careful problem analysis is necessary.
Example: In coin change problems, always choosing the largest coin may
work in one currency system but fail in another. That’s why understanding
greedy applicability is critical.
©GUVI Geeks Network Pvt. Ltd. 55
Examples: Activity Selection, Huffman Coding,
Fractional Knapsack
1. Activity Selection Problem
Choose the maximum number of activities that don’t overlap, given start
and end times
Approach: Sort activities by end time, then always pick the next
activity that starts after the current one ends
Why Greedy Works: Once an activity ends, picking the one that ends
the earliest next leaves more room for future activities.
2. Huffman Coding
Used in data compression (like ZIP files), Huffman coding assigns shorter
codes to more frequent characters
Approach: Always merge the two nodes with the lowest frequencies
Why Greedy Works: Combining the smallest frequencies ensures
minimal cost at each step.
3. Fractional Knapsack Problem
You’re allowed to take fractions of items to maximize value within a
weight limit
Approach: Sort items by value-to-weight ratio and keep adding the
best ratio until the bag is full
Why Greedy Works: The problem allows fractions, so taking the best
"bang for your buck" always helps.
©GUVI Geeks Network Pvt. Ltd. 56
Example: Minimum Coins Problem
Problem: Given coin denominations and a target amount, return the
minimum number of coins needed to make the amount.
Greedy Approach: Always pick the largest coin denomination that doesn’t
exceed the remaining amount.
def min_coins(coins, amount):
coins.sort(reverse=True) # Sort coins descending
count = 0
for coin in coins:
while amount >= coin:
amount -= coin
count += 1
return count if amount == 0 else -1
print(min_coins([1, 5, 10, 25], 30)) # Output: 2 (25 + 5)
Time Complexity: O(n) where n is the number of coin denominations
Caveat: This only works if the coin system is canonical (like U.S. coins). For
systems like [1, 3, 4], greedy may give suboptimal answers.
©GUVI Geeks Network Pvt. Ltd. 57
Chapter 8: Divide & Conquer
Divide and Conquer is a problem-solving technique where you break a big
problem into smaller sub-problems, solve them independently, and then
combine their results. This approach is especially powerful in algorithms that
involve recursion.
It follows three basic steps
Divide the problem into smaller parts
Conquer each subproblem (often recursively)
Combine the results of the subproblems to get the final answer.
This strategy is efficient, clean, and used in many high-performance
algorithms.
Understanding the Paradigm
Let’s simplify it:
Imagine you have to sort 8 books. Instead of sorting all 8 at once, you
Split them into 2 groups of 4 (Divide
Sort each group individually (Conquer
Merge the two sorted groups (Combine)
©GUVI Geeks Network Pvt. Ltd. 58
This approach reduces complexity and often makes algorithms run much
faster than brute-force methods.
Divide and Conquer typically uses recursive calls, and works well when
problems can be broken down into independent and similar smaller
problems.
Examples: Merge Sort, Binary Search, Closest
Pair of Points
1. Merge Sor
Divide the array into halves recursively
Conquer by sorting each half
Combine by merging the sorted halves.
Time Complexity: O(n log n)
Merge Sort is a classic example and works efficiently for large datasets.
2. Binary Searc
Works only on sorted arrays
Divide the array in half and compare the target with the middle
element
If not equal, search only the half where the target can exist.
Time Complexity: O(log n)
©GUVI Geeks Network Pvt. Ltd. 59
3. Closest Pair of Point
In computational geometry, Divide & Conquer helps find the two
closest points in a 2D plane much faster than checking all pairs.
Example: Power of a Number (Exponentiation
by Squaring)
Problem: Calculate a^n (a raised to power n), where n is a non-negative
integer.
Instead of multiplying a by itself n times (O(n) time), we can reduce the
number of multiplications using Divide & Conquer:
def power(a, n):
if n == 0:
return 1
elif n % 2 == 0:
half = power(a, n // 2)
return half * half
else:
return a * power(a, n - 1)
print(power(2, 10)) # Output: 1024
Time Complexity: O(log n) — much faster than linear multiplication!
This is a perfect example of how Divide & Conquer reduces effort by
solving smaller problems and reusing the results.
©GUVI Geeks Network Pvt. Ltd. 60
Chapter 9: Dynamic Programming
(Introductory)
Dynamic Programming (DP) is a powerful technique used to solve
problems that have overlapping subproblems and optimal substructure.
It’s especially useful when a naive recursive approach leads to repeated
calculations and inefficiency.
Instead of solving the same subproblem multiple times, DP solves each
subproblem just once and stores the result for future reference. This
makes many brute-force or recursive problems run much faster—often
reducing exponential time to polynomial.
Concept: Overlapping Subproblems & Optimal
Substructure
To use Dynamic Programming effectively, a problem must follow two key
properties
Overlapping Subproblems: The problem can be broken down into
smaller subproblems, which are solved repeatedly.
Example: Calculating Fibonacci(5) involves solving Fibonacci(4) and
Fibonacci(3), and both require Fibonacci(2)
Optimal Substructure: The optimal solution of the problem can be
formed using optimal solutions of its subproblems.
Example: To get the best score in a game at step n, you rely on the
best scores from previous steps.
These two characteristics help avoid redundant work and build solutions
in a structured way.
©GUVI Geeks Network Pvt. Ltd. 61
Memoization vs Tabulation
There are two main ways to implement DP
Memoization (Top-Down
You write a recursive solution and store (cache) the results of
subproblems as you compute them
Ideal when the number of subproblems solved is small and the
recursive tree is sparse.
memo = {}
def fib(n):
if n in memo: return memo[n]
if n <= 1: return n
memo[n] = fib(n - 1) + fib(n - 2)
return memo[n
Tabulation (Bottom-Up
You solve the smallest subproblems first and build up the
solution using iteration
Generally more space-efficient and faster.
def fib(n):
dp = [0, 1]
for i in range(2, n + 1):
dp.append(dp[i - 1] + dp[i - 2])
return dp[n]
Both approaches yield the same result but offer different performance
and readability benefits.
©GUVI Geeks Network Pvt. Ltd. 62
Classic Problems
Let’s explore how DP applies to some foundational problems:
Fibonacc
Problem: Find the nth number in the Fibonacci sequence
Why DP: Recursive Fibonacci causes repeated work, recalculating
the same values
With DP: We store already-calculated values, saving time.
def fib(n):
dp = [0, 1]
for i in range(2, n+1):
dp.append(dp[i-1] + dp[i-2])
return dp[n]
Time Complexity: O(n)
Space Complexity: O(n)
Longest Common Subsequence (LCS
Problem: Given two strings, find the length of their longest common
subsequence (not necessarily contiguous)
Why DP: We compare all character combinations and build results
from smaller strings.
def lcs(s1, s2):
m, n = len(s1), len(s2)
dp = [[0]*(n+1) for _ in range(m+1)]
©GUVI Geeks Network Pvt. Ltd. 63
for i in range(1, m+1):
for j in range(1, n+1):
if s1[i-1] == s2[j-1]:
dp[i][j] = 1 + dp[i-1][j-1]
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
Time Complexity: O(m × n)
Space Complexity: O(m × n)
0/1 Knapsac
Problem: Given weights and values of items, determine the maximum
value that can be carried in a knapsack of fixed capacity
Why DP: We choose to include or exclude items and store the best
results at each step.
def knapsack(W, wt, val, n):
dp = [[0]*(W+1) for _ in range(n+1)]
for i in range(1, n+1):
for w in range(1, W+1):
if wt[i-1] <= w:
dp[i][w] = max(val[i-1] + dp[i-1][w - wt[i-1]], dp[i-1][w])
else:
dp[i][w] = dp[i-1][w]
return dp[n][W]
Time Complexity: O(n × W)
Space Complexity: O(n × W)
©GUVI Geeks Network Pvt. Ltd. 64
Dynamic Programming is like solving a puzzle—once you recognize the
repeating patterns, it becomes much easier to solve efficiently. Mastering
DP opens the door to solving some of the most common and challenging
coding problems.
©GUVI Geeks Network Pvt. Ltd. 65
Chapter 10: Practice Problems by
Difficulty
Practice is the most effective way to strengthen your understanding of DSA.
In this section, we’ll cover two beginner-level problems that test your
fundamentals in strings and arrays. Try solving them yourself first before
checking the solution.
10.1 Easy Leve
Reverse a String
Problem Statement:
Given a string s, return the reversed string.
Input: "hello"
Output: "olleh"
Explanation:
You simply need to reverse the order of characters in the string. This can be
done using slicing or a loop.
Python Solution (Using Slicing):
def reverse_string(s):
def reverse_string(s):
return s[::-1]
# Test
print(reverse_string("hello")) # Output: "olleh”
©GUVI Geeks Network Pvt. Ltd. 66
Python Solution (Using Loop):
def reverse_string(s):
result = ''
for char in s:
result = char + result
return result
# Test
print(reverse_string("hello")) # Output: "olleh"
Tip: Slicing is Pythonic and short, but loops help build logic for interviews
in other languages too.
Find Largest Element in Array
Problem Statement:
Given an array of integers, find and return the largest number.
Input: [3, 11, 7, 2, 9]
Output: 11
Explanation:
You need to go through the array and compare elements to find the maximum
value.
Python Solution (Using Loop):
def find_largest(arr):
max_val = arr[0]
for num in arr[1:]:
if num > max_val:
max_val = num
return max_val
©GUVI Geeks Network Pvt. Ltd. 67
# Test
print(find_largest([3, 11, 7, 2, 9])) # Output: 11
Python Solution (Using Built-in Function):
def find_largest(arr):
return max(arr)
# Test
print(find_largest([3, 11, 7, 2, 9])) # Output: 11
Tip: While Python’s max() is handy, practicing with loops gives you control
in coding interviews.
10.2 Medium Level
Stepping into medium-level problems means building logic across multiple
steps. These problems involve a deeper understanding of arrays, hashing,
and string manipulation—great practice for interviews and coding rounds.
Two Sum
Problem Statement:
Given an array of integers nums and an integer target, return the indices of
the two numbers such that they add up to target.
Input: nums = [2, 7, 11, 15], target = 9
Output: [0, 1]
(Since nums[0] + nums[1] = 2 + 7 = 9)
Explanation:
You need to find two numbers in the array that sum up to a given target. A
brute force solution checks all pairs. A better way is to use a dictionary (hash
©GUVI Geeks Network Pvt. Ltd. 68
map) to track complements.
Optimized Python Solution:
def two_sum(nums, target):
seen = {}
for i, num in enumerate(nums):
diff = target - num
if diff in seen:
return [seen[diff], i]
seen[num] = i
# Test
print(two_sum([2, 7, 11, 15], 9)) # Output: [0, 1]
Tip: This uses a hash map to reduce time complexity to O(n).
Longest Substring Without Repeating Characters
Problem Statement:
Given a string s, find the length of the longest substring without repeating
characters.
Input: "abcabcbb"
Output: 3
(The longest substring is "abc")
Explanation:
Use a sliding window technique to keep track of characters. Expand the
window while characters are unique, and shrink it when a repeat is found.
©GUVI Geeks Network Pvt. Ltd. 69
Python Solution:
def length_of_longest_substring(s):
char_index = {}
left = max_len = 0
for right in range(len(s)):
if s[right] in char_index and char_index[s[right]] >= left:
left = char_index[s[right]] + 1
char_index[s[right]] = right
max_len = max(max_len, right - left + 1)
return max_len
# Test
print(length_of_longest_substring("abcabcbb")) # Output: 3
Tip: Mastering this pattern helps with many string and array problems
involving "windows."
10.3 Intermediate Level
Intermediate-level problems require a strong grasp of multiple DSA concepts
—such as sorting, greedy logic, recursion, or dynamic programming. These
challenges test your problem-solving depth and often appear in interviews at
top tech companies.
Merge Intervals
Problem Statement:
Given a list of intervals where each interval is represented as [start, end],
merge all overlapping intervals and return a new list of merged intervals.
Input: [[1, 3], [2, 6], [8, 10], [15, 18]]
©GUVI Geeks Network Pvt. Ltd. 70
Output: [[1, 6], [8, 10], [15, 18]]
Explanation:
Two intervals overlap if one starts before the other ends. First, sort all
intervals by start time. Then, iterate through them and merge if the
current interval overlaps with the previous.
Python Solution:
def merge_intervals(intervals):
intervals.sort(key=lambda x: x[0]) # Sort by start time
merged = []
for interval in intervals:
if not merged or merged[-1][1] < interval[0]:
merged.append(interval)
else:
merged[-1][1] = max(merged[-1][1], interval[1])
return merged
# Test
print(merge_intervals([[1, 3], [2, 6], [8, 10], [15, 18]]))
# Output: [[1, 6], [8, 10], [15, 18]]
Tip: Sorting first is the key to efficient merging. Time complexity is O(n
log n).
Word Break Problem
Problem Statement:
Given a string s and a dictionary of words wordDict, return True if s can
be segmented into a space-separated sequence of one or more
dictionary words.
Input: s = "leetcode", wordDict = ["leet", "code"]
©GUVI Geeks Network Pvt. Ltd. 71
Output: True
Explanation:
This is a classic Dynamic Programming problem. We check if prefixes of the
string exist in the dictionary and build up the solution using a DP array where
dp[i] is True if s[:i] can be segmented.
Python Solution:
def word_break(s, wordDict):
word_set = set(wordDict)
dp = [False] * (len(s) + 1)
dp[0] = True # Empty string is always "breakable"
for i in range(1, len(s) + 1):
for j in range(i):
if dp[j] and s[j:i] in word_set:
dp[i] = True
break
return dp[-1]
# Test
print(word_break("leetcode", ["leet", "code"])) # Output: True
Tip: Visualize the problem like placing breaks in the string—DP helps check
each breakpoint efficiently. Time complexity is O(n²) in the worst case.
©GUVI Geeks Network Pvt. Ltd. 72
Chapter 11: DSA in Real-World
Applications
Data Structures and Algorithms aren’t just academic topics—they form the
foundation of nearly every software application we interact with. Whether it's
a fast-loading website, an intelligent recommendation system, or a real-time
multiplayer game, DSA plays a crucial role under the hood. Let’s explore how
different tech domains apply DSA in real-world scenarios:
How DSA is used in
Web Development
Web development might seem more about design and interactivity,
but DSA silently powers critical functionality
Efficient Rendering & DOM Manipulation:
Frameworks like React use a virtual DOM, which is essentially a tree-
like structure. Optimizing updates to this tree uses concepts similar
to tree traversal algorithms
Caching & State Management:
Data structures like hash maps (for fast key-value lookups) and
queues (for async tasks) are common in front-end and back-end
logic
Search & Filters:
E-commerce sites use trie-like structures for autocomplete and
optimized search algorithms for filters and suggestions
Routing Algorithms:
In backend systems, routing requests efficiently to microservices or
database clusters often uses graph-based pathfinding logic.
©GUVI Geeks Network Pvt. Ltd. 73
App Development
Mobile apps are expected to be fast, responsive, and work with limited
resources—making DSA skills indispensable
Data Storage & Retrieval:
Storing user preferences or media files locally requires structures
like stacks (e.g., navigation stack in Android) and queues (for
managing background tasks)
Optimized Lists & Feeds:
Infinite scrolling feeds on Instagram or YouTube depend on
pagination algorithms and memory-efficient data structures
Custom UI Animations & Transitions:
Algorithms for easing curves and interpolation often borrow
concepts from numerical methods and recursive calculations
Offline Syncing:
Apps like Google Docs use efficient diffing algorithms and merge
conflict resolvers (like graph traversal) for version control.
Fun Fact: The “Undo” feature in many apps is powered by a stack, maintaining
a history of operations to revert.
Data Science & Machine Learning
At the core of every intelligent system lies a combination of statistics,
algorithms, and efficient data handling
Preprocessing Pipelines:
Processing large datasets requires sorting, searching, and filtering
using heap structures, hash maps, or tries.
©GUVI Geeks Network Pvt. Ltd. 74
Model Training:
Training ML models often involves optimization algorithms (like
Gradient Descent), which follow a structured approach similar to
greedy algorithms or dynamic programming
Data Visualization Tools:
Libraries like Plotly or Matplotlib use computational geometry to
render complex graphs efficiently, involving trees and hash maps for
scale mapping
Graph-Based Models:
In recommendation systems (like Netflix), user-item interactions are
modeled as graphs, and traversals (BFS, DFS) help in discovering
similar users or content.
Insight: Companies like Facebook and LinkedIn heavily rely on graph
algorithms to suggest friends, jobs, or content, processing billions of nodes
and edges in real-time.
Game Development
Game development is a DSA goldmine. Real-time interactivity, physics
engines, and AI require fast and efficient processing
Pathfinding Algorithms:
Characters in games like GTA or FIFA use A* (A-star) or Dijkstra’s
algorithm to navigate maps intelligently
Collision Detection:
Games use trees like Quadtrees or K-D trees to detect whether
objects on the screen are colliding or overlapping
Resource Management:
Inventory systems, cooldown timers, and animations use stacks,
©GUVI Geeks Network Pvt. Ltd. 75
queues, and hash tables
AI Behavior Trees:
Enemy bots or NPCs often follow behavior trees—structured
decision trees implemented using recursion and graph-based
planning.
Did you know? AAA games like The Last of Us or Cyberpunk 2077 use
DSA at almost every layer—from rendering graphics to multiplayer
matchmaking.
Interview Questions from FAANG & Startups
Here are the major topics asked by FAANG companies and startups for which
you can prepare
Arrays & String
Two Sum – Given an array, find two numbers that add up to a target
Product of Array Except Self – Without using division
Longest Substring Without Repeating Characters – String
manipulation and hash set usage
Rotate Array – In-place array rotation
Longest Palindromic Substring – Center expansion or DP
Linked List
Reverse a Linked List
©GUVI Geeks Network Pvt. Ltd. 76
Detect Cycle in Linked List – Floyd’s cycle detection
Merge Two Sorted List
Remove N-th Node From En
Linked List Cycle II – Find the starting node of the cycle.
Stacks & Queue
Valid Parenthese
Min Stac
Implement Queue using Stack
Largest Rectangle in Histogra
Sliding Window Maximum
Trees & Graph
Binary Tree Level Order Traversa
Lowest Common Ancestor (LCA
Validate Binary Search Tre
Number of Islands – DFS/BFS on matrix
Clone Graph
©GUVI Geeks Network Pvt. Ltd. 77
Searching & Sortin
Binary Search in Rotated Arra
Kth Largest Element in an Arra
Search a 2D Matri
Merge Interval
Quick Sort Implementation
Heaps & Hashin
Top K Frequent Element
Find Median from Data Strea
Group Anagram
Subarray Sum Equals
Longest Consecutive Sequence
Dynamic Programmin
Climbing Stair
House Robber I & I
Coin Chang
Longest Increasing Subsequence
©GUVI Geeks Network Pvt. Ltd. 78
0/1 Knapsack
Greedy, Backtracking & Advance
Jump Gam
Interval Scheduling (Greedy
N-Queen
Word Brea
Sudoku Solver
©GUVI Geeks Network Pvt. Ltd. 79
Chapter 12: DSA Tips & Learning
Strategies
A solid grip on Data Structures and Algorithms (DSA) doesn’t come overnight
—it’s built over time with consistent effort, smart strategy, and lots of
practice. Whether you’re preparing for placements, interviews, or just aiming
to strengthen your problem-solving ability, this section will guide you
through actionable learning methods, reliable platforms, and project ideas to
reinforce your DSA skills.
Consistency & Practice
To get better at DSA, there’s no shortcut—just steady, consistent practice.
Even if you solve just a couple of problems every day, the cumulative impact
over weeks and months is tremendous. Regular practice helps in identifying
patterns, building problem-solving muscle memory, and gradually improving
your logic-building skills.
You’ll also find yourself getting faster and more confident over time, which is
crucial in interviews and coding rounds. A helpful strategy is to maintain a
personal tracker or journal of the problems you’ve solved, note down
different approaches, and review your mistakes—it’ll deepen your learning
and prevent you from repeating the same errors.
One of the most important success factors in mastering DSA is consistency.
You don’t need to solve 20 problems a day—just solving 1-2 problems daily,
but doing it every day can bring long-term results.
Why consistency matters
Builds pattern recognition over time.
©GUVI Geeks Network Pvt. Ltd. 80
Helps develop muscle memory for solving common types of problems
Gradually improves your ability to optimize code and identify edge cases.
Tip: Maintain a problem-solving journal. Write down what you learned from
each problem, any mistakes made, and how you fixed them. This will help
retain concepts better than passive reading.
Importance of Problem Solving Platforms
Practicing on structured platforms makes a huge difference. These platforms
simulate real-world interview environments, often with timed contests, topic-
wise tagging, and peer discussion forums. They help you
Get exposure to a wide range of problems
Track your progress and difficulty levels
Benchmark yourself against other learners.
Another underrated benefit is how they train you to read and interpret
problem statements quickly—an essential skill in coding interviews.
Learning DSA in isolation isn’t enough—you need an environment that
challenges you, tracks your progress, and mimics real-world interview
conditions. This is exactly where problem-solving platforms become
invaluable.
These platforms not only provide a wide variety of questions but also help
you get accustomed to different coding patterns and real-time constraints.
They offer problem tags, editorials, test cases, and community discussions,
which allow you to grow beyond just solving problems—you learn how to
think like a developer.
©GUVI Geeks Network Pvt. Ltd. 81
Additionally, competing in timed challenges sharpens your speed and
accuracy, which are often key to clearing technical interviews.
Best Platforms: LeetCode, HackerRank,
CodeStudio, etc.
When you're just getting started, choosing the right platform can help you
progress faster without feeling overwhelmed. Platforms like HackerRank and
CodeStudio are beginner-friendly, guiding you through basic concepts with
step-by-step explanations and curated question sets. As your confidence
builds, you can gradually move on to LeetCode, which is widely considered the
gold standard for interview preparation, especially for top product-based
companies.
Here’s a breakdown of some of the most recommended DSA platforms
LeetCode: Best for interview prep and FAANG-level problems. Filter by
company, topic, and difficulty
HackerRank: Good for beginners, with detailed problem explanations and
beginner-friendly UI
CodeStudio (by Coding Ninjas): Offers topic-wise problems, curated
interview experiences, and guided roadmaps.
Start with HackerRank or CodeStudio if you're a beginner, then gradually move
to LeetCode as your confidence builds.
Each of these platforms has a unique value to offer. The key is to stay focused
on one or two at a time, rather than juggling too many, so you build
consistency and depth in your learning.
©GUVI Geeks Network Pvt. Ltd. 82
How to Build DSA Projects
While many learners view DSA as something confined to coding problems and
interviews, integrating it into small, functional projects is an excellent way to
deepen your understanding and demonstrate practical knowledge. Building
such projects helps you bridge the gap between theoretical problem-solving
and real-world application—making your learning more engaging and portfolio-
ready.
By turning your DSA knowledge into tangible tools or mini-apps, you build
proof of your skill that’s immensely valuable for interviews, internships, and
freelance work. Pick a project idea that aligns with your interests—whether it's
a game, utility tool, or an algorithm visualizer—and make it a fun, rewarding
challenge that pushes you beyond just coding problems.
While DSA problems are mostly conceptual and algorithmic, you can integrate
them into small projects to make your learning more interactive and portfolio-
worthy.
Examples of DSA-based Project
Text Autocomplete Tool: Uses Trie data structure
Job Scheduler: Implements priority queues using heaps
Mini Compiler: Uses stacks to check for balanced parentheses and parse
expressions
Social Network Graph: Implements graph traversal to suggest friends or
connections
File System Navigator: Tree structure + recursive navigation.
©GUVI Geeks Network Pvt. Ltd. 83
Why this matters
Demonstrates how DSA knowledge applies to real-world scenarios
Helps you build a portfolio that stands out during interviews
Sharpens your system thinking, not just algorithmic logic.
Final Advice: Pick a project related to your interest (like games, web tools, or
file systems) and implement at least 2–3 data structures or algorithms inside it.
This is one of the best ways to move from theoretical to practical knowledge.
©GUVI Geeks Network Pvt. Ltd. 84
To Conclude
By now, you've explored the essential topics that every programmer should
know—arrays, linked lists, trees, graphs, sorting algorithms, and much more.
These are the tools that will not only help you crack interviews but also build
efficient and scalable solutions in real-world projects.
Remember, mastering DSA isn't about finishing a book or course—it's about
developing a problem-solving mindset. Practice regularly, challenge yourself
with new problems, and stay consistent. Over time, you'll find yourself
thinking more logically, coding more efficiently, and growing into a confident
developer. Keep building, keep learning—your journey has just begun!
Next Step for Learning
Now, when you’ve mastered DSA, the next step would be going for an
advanced software development program. The program not only clears the
fundamentals, but also gives you a practical understanding of it by
introducing real-world applications.
If you’re looking for one such program, you must check out Professional
Certificate in AI Software Development - IITM Pravartak & MongoDB where
along with learning, you get MongoDB, IITM Pravartak and GUVI certification.
Get 3x certified!
©GUVI Geeks Network Pvt. Ltd. 85
About GUVI
GUVI (Grab Ur Vernacular Imprint), an IIT-Madras Incubated Company is
First Vernacular Ed-Tech Learning Platform. Introduced by Ex-PayPal
Employees Mr. Arun Prakash (CEO) and Mr. SP Balamurugan, and late
Sridevi Arun Prakash (co-founders) and driven by leading Industry
Experts, GUVI empowers students to master on-demand technologies,
tools, and programming skills in the comfort of their native languages like
Hindi, Tamil, Telugu, English etc.
Personalized Solutions Empowering Learners Gamified Learning
End-to-end personalized solutions in Empowering learners with tech Gamified, bite-sized videos for an
online learning, upskilling, and skills in native languages. interactive learning experience.
recruitment.
Accreditations & Partnerships
Want to know more about GUVI? Visit our website today!
Still having doubts? Book a free one-on-one consultation call with us now!
©GUVI Geeks Network Pvt. Ltd. 86
About Zen Class
Zen Class is GUVI’s specially curated learning platform that incorporates
all the Advanced Tech Career Courses like Full Stack Web Development,
Data Science, Automation Testing, Big Data & Cloud Analytics, UI/UX
Designing, and more. Zen Class provides the best industry-led curriculum
programs with assured placement guidance
Zen Class mentors are experts from leading companies like Google,
Microsoft, Flipkart, Zoho & Freshworks. Partnered with 600+ tech
companies, your chances of getting a high-paying tech job at top
companies increases.
Why choose Zen Class
Placement Guidanc
IIT-M Pravartak Certification for Advanced Programmin
Ease of Learning in native languages such as Hindi & Tamil
A rich portfolio of real-world Project
Self-paced Online Classe
Industry-led Curriculu
600+ Hiring Partner
Unlimited access to course resource
Excellent Mentor Suppor
Easy EMI options
Now, let’s know a bit about GUVI.
©GUVI Geeks Network Pvt. Ltd. 87
Thank You
www.guvi.in