๐Ÿ“Š๐Ÿงฎ๐ŸŒณ
Data Structures &
Algorithms
The Complete Beginner's Guide โ€” From Zero to Interview Ready
๐Ÿ“– Complete Notes
๐Ÿงฉ Visual Diagrams
๐Ÿ’ป Code Examples
๐ŸŽฏ LeetCode Problems
Arrays & Strings
Linked Lists
Stacks & Queues
Trees & Graphs
Sorting & Searching
Dynamic Programming
๐Ÿ“‹ Table of Contents
Navigate through all chapters and topics
Chapter 01
Arrays
The most fundamental data structure โ€” contiguous memory, indexed access, and the building block of everything.
๐Ÿ“ฆ

๐Ÿ“– What is an Array?

An array is a collection of elements stored at contiguous memory locations. Each element can be accessed directly using its index (position number starting from 0).

Think of it like a row of numbered lockers โ€” you can instantly open locker #5 without checking lockers #1 through #4.

๐Ÿ—„๏ธ Array in Memory โ€” arr = [10, 20, 30, 40, 50]
10idx 0
20idx 1
30idx 2
40idx 3
50idx 4
Memory: [0x100] [0x104] [0x108] [0x10C] [0x110] โ€” each 4 bytes apart (int)
๐Ÿ’ก

Key Insight

Because elements are contiguous, accessing any index takes O(1) time โ€” the computer calculates the exact memory address using: base_address + (index ร— element_size).

โฑ๏ธ Time Complexity Table

Operation Time Why?
Access by Index O(1) Direct memory calculation
Search (unsorted) O(n) Must check each element
Search (sorted) O(log n) Binary Search
Insert at end O(1) Append directly
Insert at beginning O(n) Shift all elements right
Delete O(n) Shift elements to fill gap

๐Ÿ”€ Technique: Two Pointers

Use two pointers (usually one at the start, one at the end) to solve problems in O(n) instead of O(nยฒ). Works great on sorted arrays.

๐ŸŽฏ Two Sum on Sorted Array โ€” Target = 9
1L ๐Ÿ‘ˆ
2 
3 
5 
7 
8R ๐Ÿ‘‰
L + R = 1 + 8 = 9 โœ… Found! | If sum < target โ†’ move L right | If sum > target โ†’ move R left
two_sum_sorted.py
def two_sum_sorted(arr, target): left, right = 0, len(arr) - 1 while left < right: current_sum = arr[left] + arr[right] if current_sum == target: return [left, right] # โœ… Found elif current_sum < target: left += 1 # Need bigger sum else: right -= 1 # Need smaller sum return [] # No solution found # Example print(two_sum_sorted([1,2,3,5,7,8], 9)) # [0, 5]

๐ŸชŸ Technique: Sliding Window

Maintain a "window" of elements and slide it across the array. Avoids recalculating from scratch each time โ€” turns O(nร—k) into O(n).

๐Ÿ“ Maximum Sum Subarray of Size 3
arr = [2, 1, 5, 1, 3, 2] โ†’ Window size k = 3
2win
1win
5win
1 
3 
2 
Window 1: 2+1+5=8 โ†’ Slide โ†’ 1+5+1=7 โ†’ 5+1+3=9 โœ… Max โ†’ 1+3+2=6
sliding_window.py
def max_sum_subarray(arr, k): window_sum = sum(arr[:k]) # First window max_sum = window_sum for i in range(k, len(arr)): window_sum += arr[i] - arr[i - k] # Slide: add new, remove old max_sum = max(max_sum, window_sum) return max_sum print(max_sum_subarray([2,1,5,1,3,2], 3)) # 9

โœ… Solved Problem: Reverse an Array In-Place

reverse_array.py
def reverse_array(arr): left, right = 0, len(arr) - 1 while left < right: arr[left], arr[right] = arr[right], arr[left] # Swap left += 1 right -= 1 return arr # Before: [1, 2, 3, 4, 5] # After: [5, 4, 3, 2, 1] # Time: O(n) | Space: O(1)
๐Ÿ”„ Step-by-Step Swap Visualization
Step 1: [1, 2, 3, 4, 5] โ†’ swap(1,5) โ†’ [5, 2, 3, 4, 1]
Step 2: [5, 2, 3, 4, 1] โ†’ swap(2,4) โ†’ [5, 4, 3, 2, 1]
Step 3: L meets R at middle โ†’ DONE โœ…
Result: [5, 4, 3, 2, 1]

๐Ÿ† LeetCode Practice โ€” Arrays

#1 Two Sum Easy
#26 Remove Duplicates from Sorted Array Easy
#53 Maximum Subarray (Kadane's Algorithm) Medium
#121 Best Time to Buy and Sell Stock Easy
#238 Product of Array Except Self Medium
#15 3Sum Medium
#42 Trapping Rain Water Hard
Chapter 02
Linked Lists
Dynamic data structures where elements are connected via pointers โ€” no contiguous memory needed.
๐Ÿ”—

๐Ÿ“– What is a Linked List?

A linked list is a linear data structure where each element (called a node) contains data and a pointer/reference to the next node. Unlike arrays, nodes can be stored anywhere in memory.

๐Ÿ”— Singly Linked List: 3 โ†’ 7 โ†’ 11 โ†’ 5 โ†’ NULL
3
โ†’
7
โ†’
11
โ†’
5
โ†’
NULL โŒ
Each box = Node { data, next_pointer } | HEAD points to first node

โœ… Advantages

  • Dynamic size โ€” grows/shrinks easily
  • O(1) insertion at head
  • No wasted memory
  • No shifting needed for insert/delete

โŒ Disadvantages

  • No random access (must traverse)
  • Extra memory for pointers
  • Not cache-friendly
  • O(n) access time

๐Ÿ”จ Node Structure & Basic Operations

linked_list.py
class Node: def __init__(self, data): self.data = data self.next = None class LinkedList: def __init__(self): self.head = None def insert_at_head(self, data): # O(1) new_node = Node(data) new_node.next = self.head self.head = new_node def insert_at_tail(self, data): # O(n) new_node = Node(data) if not self.head: self.head = new_node return curr = self.head while curr.next: curr = curr.next curr.next = new_node def delete(self, data): # O(n) if self.head and self.head.data == data: self.head = self.head.next return curr = self.head while curr.next: if curr.next.data == data: curr.next = curr.next.next return curr = curr.next def display(self): curr = self.head while curr: print(curr.data, end=" โ†’ ") curr = curr.next print("NULL")
OperationTimeNote
Insert at HeadO(1)Just change head pointer
Insert at TailO(n)Must traverse to end
Delete a NodeO(n)Must find the node first
SearchO(n)Linear scan required
Access by IndexO(n)No random access!

โœ… Solved Problem: Reverse a Linked List

This is the most asked linked list question in interviews. Use three pointers: prev, curr, and next.

๐Ÿ”„ Reversal Step-by-Step
Before: 1 โ†’ 2 โ†’ 3 โ†’ NULL

Step 1: prev=NULL, curr=1 โ†’ reverse 1's pointer โ†’ NULL โ† 1  2 โ†’ 3 โ†’ NULL
Step 2: prev=1, curr=2 โ†’ reverse 2's pointer โ†’ NULL โ† 1 โ† 2  3 โ†’ NULL
Step 3: prev=2, curr=3 โ†’ reverse 3's pointer โ†’ NULL โ† 1 โ† 2 โ† 3

After: 3 โ†’ 2 โ†’ 1 โ†’ NULL โœ…
reverse_linked_list.py
def reverse_list(head): prev = None curr = head while curr: next_node = curr.next # Save next curr.next = prev # Reverse pointer prev = curr # Move prev forward curr = next_node # Move curr forward return prev # New head # Time: O(n) | Space: O(1)

๐Ÿ† LeetCode Practice โ€” Linked Lists

#206 Reverse Linked List Easy
#21 Merge Two Sorted Lists Easy
#141 Linked List Cycle Easy
#19 Remove Nth Node From End Medium
#143 Reorder List Medium
#23 Merge K Sorted Lists Hard
Chapter 03
Stacks & Queues
Restricted access data structures โ€” LIFO and FIFO principles that power undo buttons, BFS, and much more.
๐Ÿ“š

๐Ÿ“š Stack โ€” Last In, First Out (LIFO)

A stack is like a stack of plates โ€” you can only add or remove from the top. The last plate placed on top is the first one you pick up.

๐Ÿ“š Stack Operations Visualized
PUSH(40) โฌ‡๏ธ
40 โ† TOP (new)
30
20
10
POP() โฌ†๏ธ
30 โ† TOP
20
10
Returns: 40
stack.py
# Python list works as a stack! stack = [] stack.append(10) # push stack.append(20) # push stack.append(30) # push top = stack.pop() # pop โ†’ 30 peek = stack[-1] # peek โ†’ 20 (don't remove) # All operations: O(1) โœ…

๐Ÿšถโ€โ™‚๏ธ Queue โ€” First In, First Out (FIFO)

A queue is like a line at a store โ€” the first person in line is the first one served. Elements enter from the rear and leave from the front.

๐Ÿšถโ€โ™‚๏ธ Queue: Enqueue & Dequeue
DEQUEUE โ†
10
20
30
40
โ†’ ENQUEUE
FRONT = 10 | REAR = 40
queue.py
from collections import deque queue = deque() queue.append(10) # enqueue queue.append(20) # enqueue queue.append(30) # enqueue front = queue.popleft() # dequeue โ†’ 10 peek = queue[0] # peek โ†’ 20 # All operations: O(1) โœ… # โš ๏ธ Don't use list.pop(0) โ€” it's O(n)!

โœ… Solved Problem: Valid Parentheses

๐ŸŽฏ

Problem

Given a string containing '(', ')', '{', '}', '[', ']', determine if the input string is valid. Every open bracket must have a matching close bracket in the correct order.

valid_parentheses.py
def is_valid(s): stack = [] mapping = {')': '(', '}': '{', ']': '['} for char in s: if char in mapping: top = stack.pop() if stack else '#' if mapping[char] != top: return False else: stack.append(char) return len(stack) == 0 print(is_valid("({[]})")) # True โœ… print(is_valid("({[})")) # False โŒ

๐Ÿ† LeetCode Practice โ€” Stacks & Queues

#20 Valid Parentheses Easy
#155 Min Stack Medium
#232 Implement Queue using Stacks Easy
#150 Evaluate Reverse Polish Notation Medium
#739 Daily Temperatures Medium
#84 Largest Rectangle in Histogram Hard
Chapter 04
Trees
Hierarchical data structures โ€” from file systems to databases, trees are everywhere.
๐ŸŒณ

๐ŸŒณ Binary Tree Structure

A tree where each node has at most two children: left and right. The topmost node is the root. Nodes with no children are called leaves.

๐ŸŒณ Binary Search Tree (BST) โ€” Left < Root < Right
8
โ•ฑ      โ•ฒ
3
10
โ•ฑ   โ•ฒ           โ•ฒ
1
6
14
Root: 8  |  Leaves: 1, 6, 14  |  Height: 3

๐Ÿ”„ Tree Traversals (DFS)

Three ways to visit every node in a binary tree using depth-first search. Remember the mnemonic: the name tells you when you visit the root.

๐Ÿ“ Traversal Orders for the BST above

In-Order (Left โ†’ Root โ†’ Right):   1, 3, 6, 8, 10, 14

โ†’ Gives sorted order for BST! โญ

Pre-Order (Root โ†’ Left โ†’ Right):   8, 3, 1, 6, 10, 14

โ†’ Used to copy/serialize a tree

Post-Order (Left โ†’ Right โ†’ Root):   1, 6, 3, 14, 10, 8

โ†’ Used to delete a tree

tree_traversals.py
class TreeNode: def __init__(self, val): self.val = val self.left = None self.right = None def inorder(node): # Left โ†’ Root โ†’ Right if not node: return inorder(node.left) print(node.val, end=" ") inorder(node.right) def preorder(node): # Root โ†’ Left โ†’ Right if not node: return print(node.val, end=" ") preorder(node.left) preorder(node.right) def postorder(node): # Left โ†’ Right โ†’ Root if not node: return postorder(node.left) postorder(node.right) print(node.val, end=" ")

โœ… Solved: Maximum Depth of Binary Tree

max_depth.py
def max_depth(root): if not root: return 0 left_depth = max_depth(root.left) right_depth = max_depth(root.right) return max(left_depth, right_depth) + 1 # Time: O(n) | Space: O(h) where h = height
๐ŸŒŸ

Recursion Pattern for Trees

Almost every tree problem follows: Base case (null node) โ†’ Recurse left โ†’ Recurse right โ†’ Combine results. Master this pattern!

๐Ÿ† LeetCode Practice โ€” Trees

#104 Maximum Depth of Binary Tree Easy
#226 Invert Binary Tree Easy
#100 Same Tree Easy
#102 Binary Tree Level Order Traversal Medium
#98 Validate BST Medium
#236 Lowest Common Ancestor Medium
#124 Binary Tree Maximum Path Sum Hard
Chapter 05
Sorting & Searching
The classic algorithms that form the backbone of computer science โ€” learn to arrange and find data efficiently.
๐Ÿ”„

๐Ÿ“Š Sorting Algorithms Comparison

AlgorithmBestAverageWorstSpaceStable?
Bubble Sort O(n) O(nยฒ) O(nยฒ) O(1) โœ…
Selection Sort O(nยฒ) O(nยฒ) O(nยฒ) O(1) โŒ
Insertion 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) โŒ
โš ๏ธ

Which to Use?

Small arrays (<50): Insertion Sort. General purpose: Merge Sort (stable) or Quick Sort (fast in practice). Most languages use Timsort (hybrid of merge + insertion).

๐Ÿ” Binary Search

The most important searching algorithm. Works on sorted arrays by repeatedly halving the search space. Time: O(log n) โ€” finds any element in a billion-item array in ~30 steps!

๐Ÿ” Binary Search for Target = 23

Array: [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]


Step 1: mid = arr[4] = 16  โ†’ 16 < 23 โ†’ search RIGHT half

Step 2: mid = arr[7] = 56  โ†’ 56 > 23 โ†’ search LEFT half

Step 3: mid = arr[5] = 23  โ†’ 23 == 23 โ†’ FOUND at index 5! โœ…


Only 3 comparisons instead of 10 (linear scan)!

binary_search.py
def binary_search(arr, target): left, right = 0, len(arr) - 1 while left <= right: mid = (left + right) // 2 if arr[mid] == target: return mid # โœ… Found elif arr[mid] < target: left = mid + 1 # Search right half else: right = mid - 1 # Search left half return -1 # Not found # Time: O(log n) | Space: O(1)

โœ… Solved: Merge Sort Implementation

๐Ÿงฉ Merge Sort: Divide & Conquer
[38, 27, 43, 3, 9, 82, 10]
โ†™ SPLIT โ†˜
[38, 27, 43]
[3, 9, 82, 10]
โ†™โ†˜          โ†™โ†˜
[38] [27,43]
[3,9] [82,10]
โฌ† MERGE (sort while combining) โฌ†
[3, 9, 10, 27, 38, 43, 82] โœ…
merge_sort.py
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): if left[i] <= right[j]: result.append(left[i]); i += 1 else: result.append(right[j]); j += 1 result.extend(left[i:]) result.extend(right[j:]) return result # Time: O(n log n) | Space: O(n)

๐Ÿ† LeetCode Practice โ€” Sorting & Searching

#704 Binary Search Easy
#35 Search Insert Position Easy
#33 Search in Rotated Sorted Array Medium
#215 Kth Largest Element in an Array Medium
#56 Merge Intervals Medium
#4 Median of Two Sorted Arrays Hard
Chapter 06
Hash Maps & Graphs
Key-value lookups in O(1) and exploring connected networks โ€” essential for real-world applications.
๐Ÿงฉ

๐Ÿ—บ๏ธ Hash Map (Dictionary)

A hash map stores key-value pairs and provides O(1) average lookup. It uses a hash function to convert keys into array indices.

๐Ÿ—บ๏ธ Hash Map: {"apple": 5, "banana": 3, "cherry": 8}
[0]
โ†’
๐ŸŽ "apple": 5
[1]
โ†’
empty
[2]
โ†’
๐ŸŒ "banana": 3
[3]
โ†’
๐Ÿ’ "cherry": 8
[4]
โ†’
empty
hash("apple") % 5 = 0 | hash("banana") % 5 = 2 | hash("cherry") % 5 = 3
hashmap_example.py
# Python dict IS a hash map scores = {} scores["Alice"] = 95 # Insert: O(1) scores["Bob"] = 87 # Insert: O(1) print(scores["Alice"]) # Lookup: O(1) โ†’ 95 print("Bob" in scores) # Check: O(1) โ†’ True del scores["Bob"] # Delete: O(1) # Common pattern: frequency counter def count_chars(s): freq = {} for char in s: freq[char] = freq.get(char, 0) + 1 return freq print(count_chars("hello")) # {'h': 1, 'e': 1, 'l': 2, 'o': 1}

๐Ÿ•ธ๏ธ Graphs โ€” Basics

A graph consists of vertices (nodes) connected by edges. Graphs model networks, social connections, maps, and dependencies.

๐Ÿ•ธ๏ธ Undirected Graph โ€” Adjacency List Representation
๐Ÿ…ฐ๏ธ โ€” ๐Ÿ…ฑ๏ธ โ€” ๐Ÿ…ฒ๏ธ
  |    โ•ฒ    |
๐Ÿ…ณ๏ธ    ๐Ÿ…ด๏ธ
A: [B, D]
B: [A, C, E]
C: [B, E]
D: [A]
E: [B, C]
graph_bfs_dfs.py
from collections import deque graph = { 'A': ['B', 'D'], 'B': ['A', 'C', 'E'], 'C': ['B', 'E'], 'D': ['A'], 'E': ['B', 'C'], } def bfs(graph, start): # Breadth-First Search visited = set([start]) queue = deque([start]) while queue: node = queue.popleft() print(node, end=" ") for neighbor in graph[node]: if neighbor not in visited: visited.add(neighbor) queue.append(neighbor) def dfs(graph, node, visited=None): # Depth-First Search if visited is None: visited = set() visited.add(node) print(node, end=" ") for neighbor in graph[node]: if neighbor not in visited: dfs(graph, neighbor, visited) bfs(graph, 'A') # A B D C E dfs(graph, 'A') # A B C E D

๐Ÿงฉ Intro to Dynamic Programming

DP is about breaking a problem into overlapping subproblems and storing results to avoid recalculation. Two approaches: top-down (memoization) and bottom-up (tabulation).

๐Ÿ‡ Fibonacci: Why DP Matters

Without DP (Naive Recursion):

fib(5) calls fib(4)+fib(3), fib(4) calls fib(3)+fib(2)...

fib(3) is calculated multiple times! โ†’ O(2โฟ) ๐Ÿข


With DP (Memoization):

Store fib(3) result after first calculation. Reuse it! โ†’ O(n) โšก

dp_fibonacci.py
# โŒ Naive Recursion โ€” O(2^n) def fib_naive(n): if n <= 1: return n return fib_naive(n-1) + fib_naive(n-2) # โœ… Top-Down DP (Memoization) โ€” O(n) def fib_memo(n, memo={}): if n <= 1: return n if n not in memo: memo[n] = fib_memo(n-1) + fib_memo(n-2) return memo[n] # โœ… Bottom-Up DP (Tabulation) โ€” O(n) def fib_tab(n): if n <= 1: return n dp = [0] * (n + 1) dp[1] = 1 for i in range(2, n + 1): dp[i] = dp[i-1] + dp[i-2] return dp[n] print(fib_tab(10)) # 55
๐Ÿ”ฅ

DP Recognition Pattern

Ask yourself: "Can I solve this using solutions to smaller versions of the same problem?" and "Are there overlapping subproblems?" If YES to both โ†’ use DP!

๐Ÿ† LeetCode Practice โ€” Hash Maps, Graphs & DP

#217 Contains Duplicate Easy
#242 Valid Anagram Easy
#49 Group Anagrams Medium
#200 Number of Islands Medium
#133 Clone Graph Medium
#70 Climbing Stairs Easy
#322 Coin Change Medium
#300 Longest Increasing Subsequence Medium
Quick Reference
Big-O Cheat Sheet
Master time and space complexity at a glance.
๐Ÿ“‹

โฑ๏ธ Complexity Rankings (Best โ†’ Worst)

๐Ÿ“ˆ Growth Rate Comparison
O(1) โ–ˆโ–ˆ Constant โ€” Hash lookup
O(log n) โ–ˆโ–ˆโ–ˆโ–ˆ Logarithmic โ€” Binary Search
O(n) โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ Linear โ€” Single loop
O(n log n) โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ Linearithmic โ€” Merge Sort
O(nยฒ) โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ Quadratic โ€” Nested loop
O(2โฟ) โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ Exponential โ€” Recursive fib
๐Ÿ“Œ

Rule of Thumb

If n = 10โถ (1 million), your algorithm needs to be O(n log n) or better to run in ~1 second. O(nยฒ) would take ~10ยนยฒ operations โ€” far too slow!

Data StructureAccessSearchInsertDelete
Array O(1) O(n) O(n) O(n)
Linked List O(n) O(n) O(1) O(1)
Hash Map O(1) O(1) O(1) O(1)
BST (balanced) O(log n) O(log n) O(log n) O(log n)
Stack / Queue O(n) O(n) O(1) O(1)

๐ŸŽ‰ Congratulations!

You've completed the DSA Beginner's Notes! Keep practicing on LeetCode, and remember โ€” consistency beats intensity.

๐Ÿš€๐Ÿ“ˆ๐Ÿ’ช

"The only way to learn DSA is to solve problems. Every day."

Total LeetCode Problems Listed: 40+  |  Topics Covered: 6 Chapters  |  Difficulty: Beginner โ†’ Intermediate