DSA using Java


What is DSA?


DSA (Data Structures and Algorithms) is the foundation of solving problems efficiently by organizing data and applying well-defined steps (algorithms) to process that data.
  • Data Structure (DS): A way to organize and store data efficiently for access and modification.
    Examples include: Array, LinkedList, Stack, Queue, Tree, Graph, and HashMap.
  • Algorithm: A step-by-step procedure or set of rules to solve a problem or perform a task in a finite amount of time.
    Common types include Searching (e.g., Binary Search) and Sorting (e.g., Quick Sort, Merge Sort).


Core Data Structures in Java


Data Structure

Use Case

Time Complexities (Average)

Java Implementation

Array

Index-based access, fixed size

Access: O(1), Insert/Delete: O(n)

int[] arr = new int[10];

ArrayList

Dynamic arrays

Access: O(1), Insert/Delete: O(n)

List<Integer> list = new ArrayList<>();

LinkedList

Frequent insertions/deletions

Access: O(n), Insert/Delete: O(1)

LinkedList<Integer> ll = new LinkedList<>();

Stack

LIFO (e.g., undo feature)

Push/Pop: O(1)

Stack<Integer> stack = new Stack<>();

Queue

FIFO (e.g., printer queue)

Add/Remove: O(1)

Queue<Integer> q = new LinkedList<>();

PriorityQueue

Scheduling, Top-K

Add/Remove: O(log n)

PriorityQueue<Integer> pq = new PriorityQueue<>();

HashMap

Key-value pair mapping

Access: O(1)

Map<String, Integer> map = new HashMap<>();

Tree (BST)

Hierarchical data (e.g., file systems)

Search/Insert: O(log n)

Custom class

Graph

Networks, maps

Varies

Map<Integer, List<Integer>>



Which DS to Use When

  • Array – When size is fixed and random access is needed
  • ArrayList – For dynamic arrays and frequent access
  • LinkedList – For frequent insert/delete at beginning/middle
  • Stack – For LIFO problems (undo, DFS)
  • Queue – For BFS, task scheduling
  • HashMap – For fast key-value lookups
  • PriorityQueue – For priority-based processing


Searching Algorithms 

Algorithm

Example

Time Complexity

Use Case

Linear Search

Find 5 in [3,2,5,1]

O(n)

Unsorted data

Binary Search

Find 6 in [1,2,4,6,9]

O(log n)

Sorted data


1. Linear Search

Use Case: Unsorted array, small datasets

Time Complexity: O(n)

Space Complexity: O(1)


int linearSearch(int[] arr, int target) {

    for (int i = 0; i < arr.length; i++) {

        if (arr[i] == target) return i;

    }

    return -1;

}


2. Binary Search

Use Case: Sorted arrays/lists

Time Complexity: O(log n)

Space Complexity: O(1)


int binarySearch(int[] arr, int target) {

    int low = 0, high = arr.length - 1;

    while (low <= high) {

        int mid = (low + high) / 2;

        if (arr[mid] == target) return mid;

        else if (arr[mid] < target) low = mid + 1;

        else high = mid - 1;

    }

    return -1;

}



Sorting Algorithms


Algorithm

Time

Space

Use Case

Description

Bubble Sort

O(n²)

O(1)

Teaching purposes

Repeatedly swap adjacent elements

Selection Sort

O(n²)

O(1)

Small datasets

Select smallest element each pass

Insertion Sort

O(n²)

O(1)

Nearly sorted arrays

Build sorted part incrementally

Merge Sort

O(n log n)

O(n)

Stable sort, linked lists

Divide and conquer

Quick Sort

O(n log n) avg

O(log n)

General purpose

Partition around pivot

Heap Sort

O(n log n)

O(1)

Priority-based sorting

Uses heap structure

Counting Sort

O(n+k)

O(k)

Integers in small range

Count frequencies

Radix Sort

O(nk)

O(n+k)

Integers with same digit length

Sort by digits


1. Bubble Sort

Use Case: Educational, small datasets
Time: O(n²)
Space: O(1)


void bubbleSort(int[] arr) {

    for (int i = 0; i < arr.length - 1; i++) {

        for (int j = 0; j < arr.length - i - 1; j++) {

            if (arr[j] > arr[j + 1]) {

                int temp = arr[j]; 

                arr[j] = arr[j + 1]; 

                arr[j + 1] = temp;

            }

        }

    }

}


2. Selection Sort

Use Case: Space-constrained systems
Time: O(n²)
Space: O(1)


void selectionSort(int[] arr) {

    for (int i = 0; i < arr.length - 1; i++) {

        int minIndex = i;

        for (int j = i + 1; j < arr.length; j++)

            if (arr[j] < arr[minIndex]) minIndex = j;

        int temp = arr[minIndex]; 

        arr[minIndex] = arr[i]; 

        arr[i] = temp;

    }

}


3. Insertion Sort

Use Case: Nearly sorted arrays
Time: O(n²)
Space: O(1)


void insertionSort(int[] arr) {

    for (int i = 1; i < arr.length; i++) {

        int key = arr[i];

        int j = i - 1;

        while (j >= 0 && arr[j] > key)

            arr[j + 1] = arr[j--];

        arr[j + 1] = key;

    }

}


4. Merge Sort

Use Case: Large datasets, stable sorting
Time: O(n log n)
Space: O(n)


void mergeSort(int[] arr, int l, int r) {

    if (l < r) {

        int m = (l + r) / 2;

        mergeSort(arr, l, m);

        mergeSort(arr, m + 1, r);

        merge(arr, l, m, r);

    }

}


5. Quick Sort

Use Case: General-purpose, in-place sort
Time: O(n log n) avg
Space: O(log n)


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++;

            int temp = arr[i]; 

            arr[i] = arr[j]; 

            arr[j] = temp;

        }

    }

    int temp = arr[i + 1]; 

    arr[i + 1] = arr[high]; 

    arr[high] = temp;

    return i + 1;

}


6. Heap Sort

Use Case: Priority sort, top-K problems
Time: O(n log n)
Space: O(1)


void heapSort(int[] arr) {

    PriorityQueue<Integer> heap = new PriorityQueue<>();

    for (int num : arr) 

        heap.add(num);

    for (int i = 0; i < arr.length; i++) 

        arr[i] = heap.poll();

}



Recursion & Backtracking


Recursion

A function calling itself.

int factorial(int n) {

    if (n == 0) return 1;

    return n * factorial(n - 1);

}


Backtracking

Try all possibilities, and backtrack if a choice fails.

Use Cases: N-Queens, Sudoku, Subsets



Time and Space Complexity Analysis


Big-O

Growth

Meaning

Example

O(1)

Constant

No matter the input size

Accessing array index

O(log n)

Logarithmic

Input divided each time

Binary Search

O(n)

Linear

Grows proportionally

Linear Search

O(n log n)

Linearithmic

Divide and conquer

Merge Sort, Quick Sort

O(n²)

Quadratic

Nested loops

Bubble Sort, Selection Sort

O(2ⁿ)

Exponential

Every input doubles work

Recursion Trees, Subsets

O(n!)

Factorial

Extremely high growth

Travelling Salesman, Permutations



Tree and Graph Data Structures


Structure

Java Representation

Use Case

Binary Tree

Class with left, right

Hierarchical data

BST (Binary Search Tree)

Nodes in sorted order

Search, Insert, Delete

Heap

PriorityQueue

Scheduling, Top-k

Graph

Map<Node, List<Node>>

Networks, social graphs

Trie

Custom Node class

Prefix-based search


Binary Tree (Inorder Traversal)

class TreeNode {

    int val;

    TreeNode left, right;

    TreeNode(int x) { val = x; }

}


void inorder(TreeNode root) {

    if (root == null) return;

    inorder(root.left);

    System.out.print(root.val + " ");

    inorder(root.right);

}


Binary Search Tree (Insert)

TreeNode insert(TreeNode root, int val) {

    if (root == null) return new TreeNode(val);

    if (val < root.val) root.left = insert(root.left, val);

    else root.right = insert(root.right, val);

    return root;

}


Graph – Adjacency List

Map<Integer, List<Integer>> graph = new HashMap<>();

graph.put(0, Arrays.asList(1, 2));

graph.put(1, Arrays.asList(0, 3));

graph.put(2, Arrays.asList(0));

graph.put(3, Arrays.asList(1));


Graph – BFS

void bfs(Map<Integer, List<Integer>> graph, int start) {

    Queue<Integer> queue = new LinkedList<>();

    Set<Integer> visited = new HashSet<>();

    queue.add(start);

    visited.add(start);

    while (!queue.isEmpty()) {

        int node = queue.poll();

        System.out.print(node + " ");

        for (int neighbor : graph.get(node)) {

            if (!visited.contains(neighbor)) {

                queue.add(neighbor);

                visited.add(neighbor);

            }

        }

    }

}



Other Useful DS Examples


Stack

Stack<Integer> stack = new Stack<>();

stack.push(10);

stack.pop();


Queue

Queue<Integer> queue = new LinkedList<>();

queue.add(10);

queue.poll();


PriorityQueue (Min-Heap)

PriorityQueue<Integer> pq = new PriorityQueue<>();

pq.add(5); 

pq.add(1); 

pq.add(3); 

// Outputs in ascending order


HashMap

Map<String, Integer> map = new HashMap<>();

map.put("apple", 2);

System.out.println(map.get("apple")); // 2



Advanced DSA Topics


Topic

Description

Use Case

Dynamic Programming (DP)

Store solutions of subproblems

Fibonacci, Knapsack

Greedy Algorithms

Optimal local choice

Activity selection, Huffman

Graph Algorithms

DFS, BFS, Dijkstra

Shortest path, connectivity

Sliding Window

Fixed/variable window on array

Max sum subarray

Two Pointers

Move two indices intelligently

Pair sum, reverse words

Union-Find (Disjoint Set)

Track connected components

Kruskal’s MST, DSU








Java 21 Features for DSA

Java 21 introduces several enhancements that can help in Data Structures and Algorithms (DSA), making your code more concise, efficient, and easier to reason about. Here's a complete guide to Java 21 features relevant for DSA, with examples and use cases where applicable.


Why Java 21 for DSA?


Java 21 brings modern language features that improve:

  • Code readability (e.g., pattern matching)
  • Performance (e.g., virtual threads, better APIs)
  • Safety (e.g., sealed classes)
  • Expressiveness (e.g., record patterns, switch expressions)


Java 21 Features Useful in DSA:


1. Record Patterns (Preview)

  • Use in DSA: Makes pattern matching cleaner when deconstructing nested records (e.g., trees, pairs).
  • Example:

        record Pair(int x, int y) {}

        static void print(Pair p) {

            switch (p) {

                case Pair(int x, int y) -> System.out.println(x + y);

            }

        }


2. Pattern Matching for switch (Standard)

  • Use in DSA: Provides clean and expressive conditional logic, replacing verbose if-else or instanceof chains.
  • Example:

        static String typeCheck(Object o) {

            return switch (o) {

                case Integer i -> "Integer: " + i;

                case String s -> "String: " + s;

                default -> "Unknown";

            };

        }


3. Unnamed Variables and Patterns (Preview)

  • Use in DSA: Helps ignore unnecessary variables, especially useful in lambda, stream, and filter operations.
  • Example:

        if (obj instanceof String _) {

            System.out.println("It's a string");

        }


4. Sequenced Collections (Standard)

  • Use in DSA: Preserves insertion order and allows access from the first or last element, useful for queue/stack-like problems.
  • New Interfaces:
    • SequencedCollection
    • SequencedSet
    • SequencedMap
  • Example:

        SequencedMap<Integer, String> map = new LinkedHashMap<>();

        map.put(1, "A");

        map.put(2, "B");

        map.firstEntry(); 

        map.firstEntry(); // {1=A}

        map.lastEntry();  // {2=B}


5. Virtual Threads (Standard)

  • Use in DSA: Useful for parallel algorithms or concurrent data structure problems.
  • Lightweight threads that enable massive concurrency.
  • Example:

        Runnable task = () -> System.out.println(Thread.currentThread());

        Thread.startVirtualThread(task);


6. Scoped Values (Preview)

  • Use in DSA: A safer alternative to thread-local variables, especially in recursive or parallel algorithms to pass values down the stack safely.


7. String Templates (Preview)

  • Use in DSA: Helpful for debugging and visually displaying data structures like trees or graphs.
  • Example:

        String name = "DFS", status = "Completed";

        String msg = STR."Algorithm: \{name}, Status: \{status}";


8. JEP 430 – String Templates, JEP 443 – Unnamed Patterns, JEP 445 – Unnamed Classes

Together, these make writing quick DSA tests, interview code, and proofs more efficient and cleaner. 

Summary Table

Feature

Use in DSA

Record Patterns

Simplify deconstructing records (e.g., trees, tuples)

Pattern Matching in Switch

Clean control flow in algorithms

Unnamed Variables/Patterns

Cleaner loops and stream operations

Sequenced Collections

Order-sensitive data structures (queues, stacks, LRU)

Virtual Threads

Parallel execution (e.g., multithreaded sorting)

Scoped Values

Thread-safe value passing in recursion/concurrency

String Templates

Easier debugging of complex data structures