Code-Alongs

Code-Along 1: Sorting

Sorting Algorithms Overview

In this code-along, we'll implement and analyze different sorting algorithms, focusing on their time complexity and practical applications.

Objectives

  • Implement selection sort and understand its O(n²) time complexity
  • Implement merge sort and understand its O(n log n) time complexity
  • Compare the performance of different sorting algorithms
  • Analyze when to use each sorting algorithm based on data characteristics

Selection Sort Implementation

Selection sort works by repeatedly finding the minimum element from the unsorted part of the array and putting it at the beginning.

public static void selectionSort(int[] arr) {
    int n = arr.length;
    
    // One by one move boundary of unsorted subarray
    for (int i = 0; i < n - 1; i++) {
        // Find the minimum element in unsorted array
        int minIdx = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIdx]) {
                minIdx = j;
            }
        }
        
        // Swap the found minimum element with the first element
        int temp = arr[minIdx];
        arr[minIdx] = arr[i];
        arr[i] = temp;
    }
}

Merge Sort Implementation

Merge sort is a divide and conquer algorithm that divides the input array into two halves, recursively sorts them, and then merges the sorted halves.

public static void mergeSort(int[] arr, int left, int right) {
    if (left < right) {
        // Find the middle point
        int mid = left + (right - left) / 2;
        
        // Sort first and second halves
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);
        
        // Merge the sorted halves
        merge(arr, left, mid, right);
    }
}

private static void merge(int[] arr, int left, int mid, int right) {
    // Find sizes of two subarrays to be merged
    int n1 = mid - left + 1;
    int n2 = right - mid;
    
    // Create temp arrays
    int[] L = new int[n1];
    int[] R = new int[n2];
    
    // Copy data to temp arrays
    for (int i = 0; i < n1; ++i) {
        L[i] = arr[left + i];
    }
    for (int j = 0; j < n2; ++j) {
        R[j] = arr[mid + 1 + j];
    }
    
    // Merge the temp arrays
    int i = 0, j = 0;
    int k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k] = L[i];
            i++;
        } else {
            arr[k] = R[j];
            j++;
        }
        k++;
    }
    
    // Copy remaining elements of L[] if any
    while (i < n1) {
        arr[k] = L[i];
        i++;
        k++;
    }
    
    // Copy remaining elements of R[] if any
    while (j < n2) {
        arr[k] = R[j];
        j++;
        k++;
    }
}

Code-Along 2: Recursion

Recursion Fundamentals

In this code-along, we'll explore recursion and implement various recursive algorithms to solve problems.

Objectives

  • Understand the core concepts of recursion (base case and recursive case)
  • Implement classic recursive algorithms like factorial and Fibonacci
  • Analyze the time and space complexity of recursive solutions
  • Compare recursive implementations with iterative alternatives

Factorial Implementation

The factorial function is a classic example of recursion, computing the product of all positive integers less than or equal to n.

public static int factorial(int n) {
    // Base case
    if (n == 0 || n == 1) {
        return 1;
    }
    
    // Recursive case
    return n * factorial(n - 1);
}

Fibonacci Implementation

The Fibonacci sequence is another classic recursive problem, where each number is the sum of the two preceding ones.

public static int fibonacci(int n) {
    // Base cases
    if (n == 0) return 0;
    if (n == 1) return 1;
    
    // Recursive case
    return fibonacci(n - 1) + fibonacci(n - 2);
}

Optimized Fibonacci with Memoization

A more efficient implementation using memoization to avoid redundant calculations.

public static int fibonacciMemoized(int n, int[] memo) {
    // Base cases
    if (n == 0) return 0;
    if (n == 1) return 1;
    
    // Check if already calculated
    if (memo[n] != 0) {
        return memo[n];
    }
    
    // Calculate and store result
    memo[n] = fibonacciMemoized(n - 1, memo) + fibonacciMemoized(n - 2, memo);
    return memo[n];
}