Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
108 views89 pages

The Data Structures and Algorithms - Ebook

The Data Structures and Algorithms Handbook is an introductory eBook designed to help beginners understand the foundational concepts of DSA, which are crucial for coding interviews and software development. It covers various topics including algorithms, data structures, and their applications, with practical examples and exercises to reinforce learning. The eBook emphasizes the importance of mastering DSA for a successful tech career and provides a structured approach to learning.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
108 views89 pages

The Data Structures and Algorithms - Ebook

The Data Structures and Algorithms Handbook is an introductory eBook designed to help beginners understand the foundational concepts of DSA, which are crucial for coding interviews and software development. It covers various topics including algorithms, data structures, and their applications, with practical examples and exercises to reinforce learning. The eBook emphasizes the importance of mastering DSA for a successful tech career and provides a structured approach to learning.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 89

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

You might also like