Data Structures &
Algorithms Complete Notes
Arrays ยท Linked Lists ยท Trees ยท Graphs ยท Sorting ยท Searching ยท Dynamic Programming โ Master DSA in Easy English
Basics & Time Complexity
Foundation of Data Structures and Algorithm Analysis
What is a Data Structure?
A data structure is a way to organize, store, and manage data efficiently. Different data structures are optimized for different operations.
Why Study DSA?
- Write faster and more efficient code
- Pass technical interviews (DSA is 70% of coding interviews)
- Understand how databases, OS, and compilers work
- Optimize applications for scale
Big O Notation โ Time Complexity
Big O describes the worst-case runtime as input size grows. It ignores constants and lower-order terms.
| Notation | Name | Example Operations | Speed |
|---|---|---|---|
| O(1) | Constant | Array access by index, hash lookup | โก Excellent |
| O(log n) | Logarithmic | Binary search | โ Great |
| O(n) | Linear | Simple loop, linear search | โ Good |
| O(n log n) | Linearithmic | Merge sort, quick sort | โ Fair |
| O(nยฒ) | Quadratic | Bubble sort, nested loops | โ Bad |
| O(nยณ) | Cubic | Triple nested loops | โ Worse |
| O(2โฟ) | Exponential | Recursion without memoization | โโ Terrible |
| O(n!) | Factorial | Permutations | โโโ Impossible |
Calculating Time Complexity
// Example 1: O(1) - Constant int getFirst(int[] arr) { return arr[0]; // Always 1 operation, regardless of array size } // Example 2: O(n) - Linear void printAll(int[] arr) { for(int i = 0; i < arr.length; i++) { // Loop runs n times System.out.println(arr[i]); } } // Example 3: O(nยฒ) - Quadratic void bubbleSort(int[] arr) { for(int i = 0; i < arr.length; i++) { // Outer loop: n times for(int j = 0; j < arr.length-1; j++) { // Inner loop: n times // n ร n = O(nยฒ) } } }
Space Complexity
Space complexity measures the extra memory used by an algorithm (not counting input).
// O(1) space - only uses fixed variables int sum(int[] arr) { int total = 0; // Just 1 variable for(int x : arr) total += x; return total; } // O(n) space - creates new array int[] duplicate(int[] arr) { int[] copy = new int[arr.length]; // New array of size n System.arraycopy(arr, 0, copy, 0, arr.length); return copy; }
Arrays & Strings
Fundamental Data Structures with Contiguous Memory
Array Basics
An array is a fixed-size collection of elements stored in contiguous memory. Fast access but slow insertion/deletion.
| Operation | Time Complexity | Notes |
|---|---|---|
| Access element | O(1) | Direct memory access via index |
| Search (unsorted) | O(n) | Must check each element |
| Search (sorted) | O(log n) | Binary search |
| Insert at end | O(1) | If space available |
| Insert at middle | O(n) | Shift remaining elements |
| Delete | O(n) | Shift remaining elements |
2D Arrays (Matrices)
A 2D array is an array of arrays. Think of it as a grid with rows and columns.
// 2D array declaration and access int[][] matrix = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; // Access element at row 1, column 2 int value = matrix[1][2]; // Returns 6 // Traverse 2D array for(int i = 0; i < matrix.length; i++) { for(int j = 0; j < matrix[i].length; j++) { System.out.print(matrix[i][j] + " "); } System.out.println(); }
Common Array Problems
// Find if array is sorted boolean isSorted(int[] arr) { for(int i = 0; i < arr.length - 1; i++) { if(arr[i] > arr[i+1]) return false; } return true; } // Reverse array using two pointers void reverse(int[] arr) { int left = 0, right = arr.length - 1; while(left < right) { // Swap int temp = arr[left]; arr[left] = arr[right]; arr[right] = temp; left++; right--; } }
String Manipulation
Strings are arrays of characters. Many array techniques apply to strings.
// Check if palindrome boolean isPalindrome(String s) { int left = 0, right = s.length() - 1; while(left < right) { if(s.charAt(left) != s.charAt(right)) { return false; } left++; right--; } return true; } // Reverse string String reverse(String s) { char[] chars = s.toCharArray(); int left = 0, right = chars.length - 1; while(left < right) { char temp = chars[left]; chars[left] = chars[right]; chars[right] = temp; left++; right--; } return new String(chars); }
Linked Lists
Dynamic Data Structure with Non-Contiguous Memory
Linked List Basics
A linked list is a dynamic collection where elements (nodes) are linked via pointers. Unlike arrays, it uses non-contiguous memory.
// Node structure class Node { int data; Node next; Node(int data) { this.data = data; this.next = null; } } // Simple linked list class LinkedList { Node head; // Insert at beginning - O(1) void insertFirst(int data) { Node newNode = new Node(data); newNode.next = head; head = newNode; } }
Linked List Operations
| Operation | Time | Space | Notes |
|---|---|---|---|
| Access | O(n) | O(1) | Must traverse from head |
| Search | O(n) | O(1) | Linear search |
| Insert at head | O(1) | O(1) | Very fast |
| Insert at tail | O(n) | O(1) | Need to find tail |
| Delete at head | O(1) | O(1) | Very fast |
| Delete at tail | O(n) | O(1) | Need previous node |
Traversal & Display
// Display all elements void display() { Node current = head; while(current != null) { System.out.print(current.data + " โ "); current = current.next; } System.out.println("null"); }
Doubly Linked List
Each node has pointers to both next AND previous nodes. Allows backward traversal.
// Doubly linked list node class DNode { int data; DNode next, prev; DNode(int data) { this.data = data; this.next = this.prev = null; } }
Circular Linked List
The last node points back to the first node, forming a circle. No null at the end.
Stack & Queue
Specialized Data Structures with Restricted Access Patterns
Stack (LIFO โ Last In First Out)
Think of a stack of plates. You add and remove from the same end (top). Last plate added is first one removed.
| Operation | Time | Description |
|---|---|---|
| Push (add) | O(1) | Add element to top |
| Pop (remove) | O(1) | Remove from top |
| Peek (view) | O(1) | View top without removing |
| isEmpty | O(1) | Check if empty |
// Stack implementation class Stack { private int[] items; private int top = -1; Stack(int capacity) { items = new int[capacity]; } void push(int value) { items[++top] = value; } int pop() { return items[top--]; } int peek() { return items[top]; } boolean isEmpty() { return top == -1; } }
Stack Applications
- Browser history: Back button uses stack
- Undo/Redo: All editors use stacks
- Function calls: Call stack in programming
- Expression evaluation: Check balanced parentheses
- Parsing: Compilers use stacks
// Check balanced parentheses using stack boolean isBalanced(String s) { Stack<Character> st = new Stack<>(); for(char c : s.toCharArray()) { if(c == '(' || c == '[' || c == '{') { st.push(c); } else if(c == ')' || c == ']' || c == '}') { if(st.isEmpty()) return false; st.pop(); } } return st.isEmpty(); }
Queue (FIFO โ First In First Out)
Think of a queue at a movie theater. You add at the back (rear) and remove from front. First person in is first served.
| Operation | Time | Description |
|---|---|---|
| Enqueue | O(1) | Add to rear |
| Dequeue | O(1) | Remove from front |
| Peek | O(1) | View front element |
Queue Applications
- CPU scheduling: Process queue
- Printer queue: Print jobs in order
- BFS graph traversal: Level-order traversal
- Message queues: Kafka, RabbitMQ
Priority Queue
Elements are removed based on priority, not just insertion order. Uses a heap internally.
Trees
Hierarchical Data Structure for Organizing Information
Tree Basics
A tree is a hierarchical data structure with a root node and child nodes. It's a connected graph with no cycles.
A (root)
/|\
B C D
/| |
E F G
Terminology:
- Root: Top node (A)
- Parent: Node with children (A is parent of B, C, D)
- Children: Nodes under a parent (B, C are children of A)
- Leaf: Node with no children (E, F, G)
- Subtree: Tree below any node
- Height: Distance from node to deepest leaf
- Depth: Distance from root to nodeBinary Tree
Each node has at most 2 children (left and right).
// Binary tree node class TreeNode { int data; TreeNode left, right; TreeNode(int data) { this.data = data; } }
Tree Traversals
Four main ways to visit all nodes:
| Traversal | Order | Example (see diagram) | Use Case |
|---|---|---|---|
| Inorder | Left โ Root โ Right | E, B, F, A, C, G, D | BST (sorted output) |
| Preorder | Root โ Left โ Right | A, B, E, F, C, D, G | Copy tree, prefix notation |
| Postorder | Left โ Right โ Root | E, F, B, C, G, D, A | Delete tree |
| Level Order | Level by level | A, B, C, D, E, F, G | BFS |
// Inorder traversal (recursive) void inorder(TreeNode root) { if(root == null) return; inorder(root.left); System.out.print(root.data + " "); inorder(root.right); } // Level order traversal (using queue for BFS) void levelOrder(TreeNode root) { Queue<TreeNode> q = new LinkedList<>(); q.add(root); while(!q.isEmpty()) { TreeNode node = q.poll(); System.out.print(node.data + " "); if(node.left != null) q.add(node.left); if(node.right != null) q.add(node.right); } }
Tree Properties
- Perfect Binary Tree: All levels completely filled
- Complete Binary Tree: All levels filled except possibly the last
- Balanced Binary Tree: Height difference โค 1 for all subtrees
- Full Binary Tree: Every node has 0 or 2 children
Binary Search Trees
Ordered Trees for Efficient Searching
BST Property
A Binary Search Tree is a binary tree where:
- Left subtree has values smaller than root
- Right subtree has values greater than root
- Both left and right subtrees are also BSTs
BST Operations
| Operation | Best Case | Worst Case | Notes |
|---|---|---|---|
| Search | O(log n) | O(n) | Worst if tree is skewed |
| Insert | O(log n) | O(n) | Always at leaf |
| Delete | O(log n) | O(n) | Complex if node has 2 children |
| Inorder Traversal | O(n) | O(n) | Gives sorted output |
// BST Search TreeNode search(TreeNode root, int key) { if(root == null) return null; if(key == root.data) return root; else if(key < root.data) return search(root.left, key); else return search(root.right, key); } // BST Insert TreeNode insert(TreeNode root, int value) { if(root == null) return new TreeNode(value); if(value < root.data) { root.left = insert(root.left, value); } else if(value > root.data) { root.right = insert(root.right, value); } return root; }
BST Delete โ 3 Cases
- Node is leaf: Simply remove it
- Node has 1 child: Replace with its child
- Node has 2 children: Replace with inorder successor (smallest in right subtree), then delete successor
Balanced BSTs โ AVL & Red-Black Trees
Regular BSTs can become skewed. Balanced trees ensure O(log n) for all operations.
Heaps & Priority Queues
Complete Binary Trees with Heap Property
Heap Property
A heap is a complete binary tree where each parent is greater (max heap) or smaller (min heap) than its children.
- Max Heap: Parent โฅ Children. Root is maximum.
- Min Heap: Parent โค Children. Root is minimum.
- Complete Binary Tree: All levels filled except possibly last, which is left-aligned.
Heap Operations
| Operation | Time | Description |
|---|---|---|
| Insert | O(log n) | Add at end, bubble up |
| Delete Min/Max | O(log n) | Remove root, move last to root, bubble down |
| Peek Min/Max | O(1) | View root |
| Heapify | O(n) | Build heap from array |
// Min Heap using array class MinHeap { private int[] heap; private int size = 0; // Parent: (i-1)/2, Left: 2i+1, Right: 2i+2 void insert(int value) { heap[size] = value; bubbleUp(size); size++; } void bubbleUp(int index) { while(index > 0) { int parent = (index - 1) / 2; if(heap[index] < heap[parent]) { // Swap int temp = heap[index]; heap[index] = heap[parent]; heap[parent] = temp; index = parent; } else break; } } }
Heap Applications
- Priority Queue: Process elements by priority
- Dijkstra's algorithm: Shortest path
- Heap Sort: Efficient sorting O(n log n)
- Top K elements: Find k largest/smallest efficiently
Graphs
Non-Linear Data Structure for Connected Data
Graph Basics
A graph consists of vertices (nodes) and edges (connections). Can be directed or undirected, weighted or unweighted.
// Types of Graphs: 1. Directed (Digraph): AโB (one direction) 2. Undirected: AโB (both directions) 3. Weighted: Edge has a value/cost 4. Unweighted: All edges equal // Density: - Sparse: Few edges (n ~ m, where m is edges) - Dense: Many edges (m ~ nยฒ)
Graph Representations
| Representation | Space | Add Edge | Check Edge |
|---|---|---|---|
| Adjacency Matrix | O(Vยฒ) | O(1) | O(1) |
| Adjacency List | O(V+E) | O(1) | O(E) |
| Edge List | O(E) | O(1) | O(E) |
// Adjacency List representation Map<Integer, List<Integer>> graph = new HashMap<>(); // Add edge from u to v void addEdge(int u, int v) { graph.putIfAbsent(u, new ArrayList<>()); graph.get(u).add(v); }
Graph Traversals
// BFS Traversal void bfs(int start) { Queue<Integer> q = new LinkedList<>(); Set<Integer> visited = new HashSet<>(); q.add(start); visited.add(start); while(!q.isEmpty()) { int node = q.poll(); System.out.print(node + " "); for(int neighbor : graph.getOrDefault(node, new ArrayList<>())) { if(!visited.contains(neighbor)) { visited.add(neighbor); q.add(neighbor); } } } } // DFS Traversal (Recursive) void dfs(int node, Set<Integer> visited) { visited.add(node); System.out.print(node + " "); for(int neighbor : graph.getOrDefault(node, new ArrayList<>())) { if(!visited.contains(neighbor)) { dfs(neighbor, visited); } } }
Important Graph Algorithms
- Dijkstra: Shortest path (weighted, non-negative)
- Bellman-Ford: Shortest path (handles negative weights)
- Floyd-Warshall: All pairs shortest path
- Kruskal/Prim: Minimum Spanning Tree
- Topological Sort: DAG ordering
Sorting Algorithms
Methods to Arrange Elements in Order
Sorting Comparison
| Algorithm | Best | Average | Worst | Space | Stable |
|---|---|---|---|---|---|
| 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) | โ |
| Heap Sort | O(n log n) | O(n log n) | O(n log n) | O(1) | โ |
Bubble Sort โ O(nยฒ)
Repeatedly swap adjacent elements if they're in wrong order. Simplest but slowest.
// Bubble Sort void bubbleSort(int[] arr) { int n = arr.length; for(int i = 0; i < n - 1; i++) { // Last i elements are already sorted for(int j = 0; j < n - i - 1; j++) { if(arr[j] > arr[j + 1]) { // Swap int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } }
Quick Sort โ O(n log n) average
Divide-and-conquer. Pick pivot, partition, recursively sort. Fastest in practice.
// Quick Sort void quickSort(int[] arr, int low, int high) { if(low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi + 1, high); } } int partition(int[] arr, int low, int high) { int pivot = arr[high]; int i = low - 1; for(int j = low; j < high; j++) { if(arr[j] < pivot) { i++; // Swap arr[i] and arr[j] int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // Swap arr[i+1] and arr[high] int temp = arr[i + 1]; arr[i + 1] = arr[high]; arr[high] = temp; return i + 1; }
Merge Sort โ O(n log n) guaranteed
Divide-and-conquer. Divide array in half, recursively sort, merge. Stable and predictable.
// Merge Sort void mergeSort(int[] arr, int left, int right) { if(left < right) { int mid = left + (right - left) / 2; mergeSort(arr, left, mid); mergeSort(arr, mid + 1, right); merge(arr, left, mid, right); } }
Searching Algorithms
Methods to Find Elements Efficiently
Linear Search โ O(n)
Check each element one by one. Works on unsorted arrays.
// Linear Search int linearSearch(int[] arr, int target) { for(int i = 0; i < arr.length; i++) { if(arr[i] == target) { return i; } } return -1; // Not found }
Binary Search โ O(log n)
Eliminate half the remaining elements each time. Requires sorted array. Much faster for large data.
// Binary Search (Iterative) int binarySearch(int[] arr, int target) { int left = 0, right = arr.length - 1; while(left <= right) { int mid = left + (right - left) / 2; if(arr[mid] == target) { return mid; } else if(arr[mid] < target) { left = mid + 1; // Search right half } else { right = mid - 1; // Search left half } } return -1; // Not found } // Binary Search (Recursive) int binarySearchRec(int[] arr, int target, int left, int right) { if(left > right) return -1; int mid = left + (right - left) / 2; if(arr[mid] == target) return mid; else if(arr[mid] < target) return binarySearchRec(arr, target, mid + 1, right); else return binarySearchRec(arr, target, left, mid - 1); }
Search Comparison
| Algorithm | Time | Space | Requires Sorting |
|---|---|---|---|
| Linear Search | O(n) | O(1) | No |
| Binary Search | O(log n) | O(1) | Yes |
| Hash Lookup | O(1) avg | O(n) | No |
Binary Search Variations
- Find first occurrence: Search left half even if found
- Find last occurrence: Search right half even if found
- Find smallest โฅ target: Lower bound
- Find largest โค target: Upper bound
Dynamic Programming
Optimization Technique Breaking Problems into Overlapping Subproblems
What is Dynamic Programming?
DP solves problems by breaking them into overlapping subproblems and storing results (memoization) to avoid recomputation.
Fibonacci Sequence โ Classic Example
// Naive Recursion โ EXPONENTIAL O(2โฟ) โ VERY SLOW int fib(int n) { if(n <= 1) return n; return fib(n-1) + fib(n-2); // Recomputes same values! } // DP with Memoization โ O(n) โ MUCH FASTER int fib(int n, int[] memo) { if(n <= 1) return n; if(memo[n] != 0) return memo[n]; // Return cached result memo[n] = fib(n-1, memo) + fib(n-2, memo); return memo[n]; } // DP with Tabulation (bottom-up) โ O(n) โ BEST int fib(int n) { int[] dp = new int[n+1]; dp[0] = 0; dp[1] = 1; for(int i = 2; i <= n; i++) { dp[i] = dp[i-1] + dp[i-2]; } return dp[n]; }
0/1 Knapsack Problem
Given items with weight and value, fill knapsack of capacity W to maximize value.
// 0/1 Knapsack using DP int knapsack(int[] weights, int[] values, int W) { int n = weights.length; int[][] dp = new int[n+1][W+1]; // dp[i][w] = max value with first i items and capacity w for(int i = 1; i <= n; i++) { for(int w = 1; w <= W; w++) { if(weights[i-1] <= w) { // Include or exclude item dp[i][w] = Math.max( values[i-1] + dp[i-1][w-weights[i-1]], // Include dp[i-1][w] // Exclude ); } else { dp[i][w] = dp[i-1][w]; // Can't include } } } return dp[n][W]; }
Common DP Problems
- Longest Common Subsequence (LCS): O(mรn)
- Edit Distance: O(mรn)
- Coin Change: O(nรW)
- Maximum Subarray Sum: O(n)
- Rod Cutting: O(nยฒ)
Greedy Algorithms
Making Locally Optimal Choices for Global Solution
Greedy Approach
Greedy algorithms make the best choice at each step, hoping to find a global optimum. Fast but not always optimal.
Activity Selection Problem
Select maximum non-overlapping activities.
// Activity Selection โ Greedy approach void activitySelection(List<Activity> activities) { // Sort by finish time activities.sort((a, b) -> a.finish - b.finish); List<Activity> selected = new ArrayList<>(); selected.add(activities.get(0)); int lastFinish = activities.get(0).finish; for(int i = 1; i < activities.size(); i++) { if(activities.get(i).start >= lastFinish) { selected.add(activities.get(i)); lastFinish = activities.get(i).finish; } } System.out.println("Selected " + selected.size() + " activities"); }
Huffman Coding
Assign variable-length codes based on character frequency. More frequent โ shorter code.
Fractional Knapsack
Unlike 0/1 knapsack, you can take fractional items. Greedy works here!
// Greedy approach: Take items by value/weight ratio double knapsack(int[] values, int[] weights, int W) { // Sort by value/weight ratio in descending order // Take items greedily until capacity full } Time Complexity: O(n log n) due to sorting
Greedy Problems
- Coin Change (minimum coins): O(n log n)
- Interval Scheduling: O(n log n)
- Job Sequencing: O(n log n)
- Minimum Spanning Tree (Kruskal/Prim): O(E log V)
๐ Congratulations!
You've completed the comprehensive DSA course! Now practice these concepts on LeetCode, HackerRank, or CodeSignal.
Quick Reference โ Time Complexities
Bookmark this for quick lookup during coding
| Data Structure / Operation | Best | Average | Worst |
|---|---|---|---|
| Array Access | O(1) | O(1) | O(1) |
| Linked List Search | O(1) | O(n) | O(n) |
| Binary Search Tree Search | O(log n) | O(log n) | O(n) |
| Hash Table Insert | O(1) | O(1) | O(n) |
| Heap Insert/Delete | O(log n) | O(log n) | O(log n) |
| Graph BFS/DFS | O(V+E) | O(V+E) | O(V+E) |
| Merge Sort | O(n log n) | O(n log n) | O(n log n) |
| Quick Sort | O(n log n) | O(n log n) | O(nยฒ) |
๐ Pak Notes Hub โ DSA Complete Notes | University Level | BS CS / BS IT
For corrections and suggestions: support@paknoteshub.com

