Bubble sort

šŸ“Œ Key Idea: In every pass, the maximum element goes (bubble up) to its correct position at the end of the array.

void bubble(vector<int> &arr)
{
    int n = arr.size();
 
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n - 1; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                swap(arr[j], arr[j + 1]);
            }
        }
    }
}
 
/*
Optimizations:
 
1. Outer loop runs only till n-1
   * After (n-1) passes, the array is guaranteed to be sorted.
 
2. Inner loop runs till (n-1-i)
   * After every pass, the largest element moves to its correct position.
   * Therefore, the last i elements are already sorted and can be skipped.
 
3. Early termination
   * If no swap occurs during a pass, the array is already sorted.
   * Stop further processing using a flag.
*/
void bubbleOptimized(vector<int> &arr)
{
    int n = arr.size();
 
    for (int i = 0; i < n - 1; i++)
    {
        bool isSorted = true;
 
        for (int j = 0; j < n - 1 - i; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                swap(arr[j], arr[j + 1]);
                isSorted = false;
            }
        }
 
        if (isSorted)
            break;
    }
}
CaseTime Complexity
Best Case (already sorted) isSorted = trueO(n)
Worst CaseO(n²)

Selection Sort

šŸ“Œ Key Idea: In every pass, find the smallest element correct position in the sorted postion at front

void selectionsort (int a[], int n) {
    for (int i = 0; i < n - 1 ; i++) {
        //step 1 --> temperory set
        int imin = i; // ith position
 
        //step 2 --> finding imin
        for (int j = i + 1 ; j < n ; j++) {
            if (a[j] < a[imin])
            {
                imin = j;
            }
        }
 
        //step 3 --> swaping
        swap(a[i], a[imin]);
    }
}


Insertion Sort

šŸ“Œ Key Idea: Build the sorted portion one element at a time by inserting the current element into its correct position among the already sorted elements.

void insertionSort(vector<int>& arr) {
    int n = arr.size();
 
    // [Sorted Array] [Curr Element] [Unsorted Array]
 
    // Outer loop -> pick elements one by one
    for (int i = 1; i < n; i++) {
 
        // Inner loop -> move current element to correct position in sorted Array    
        for (int j = i; j > 0; j--) {
 
            // Swap if left element is bigger
            if (arr[j] < arr[j - 1]) {
                swap(arr[j], arr[j - 1]);
            }
            else {
                break; // already sorted
            }
        }
    }
}
 
[5, 3, 4, 1]
 
Pass 1:
[3, 5, 4, 1]
 
Pass 2:
[3, 4, 5, 1]
 
Pass 3:
[1, 3, 4, 5]
CaseTime Complexity
Best Case (already sorted)O(n)
Worst Case (reverse sorted)O(n²)

Stability of Sorting Algorithms

A sorting algorithm is stable if equal elements maintain their relative order after sorting.

Example:

[(5,A), (3,X), (5,B)]

[(3,X), (5,A), (5,B)] # Stable sorted

A remains before B, so the sort is stable.

Note

Stable āœ… = Bubble, Insersion, Merge Not Stable āŒ = selection, quick, HeapSort

Merge Sort

šŸ“Œ Key Idea: Divide the array into two halves, recursively sort both halves, then merge the sorted halves.

void merge(vector<int>& nums, int l, int mid, int r)
{
    vector<int> sorted;
 
    int l1 = l;
    int r1 = mid;
 
    int l2 = mid + 1;
    int r2 = r;
 
    while (l1 <= r1 && l2 <= r2)
    {
        if (nums[l1] <= nums[l2])
            sorted.push_back(nums[l1++]);
        else
            sorted.push_back(nums[l2++]);
    }
 
    while (l1 <= r1)
        sorted.push_back(nums[l1++]);
 
    while (l2 <= r2)
        sorted.push_back(nums[l2++]);
 
    for (auto &ele : sorted)
        nums[l++] = ele;
}
 
void mergeSort(vector<int>& nums, int l, int r)
{
    if (l >= r)
        return;
 
    int mid = l + (r - l) / 2;
 
    mergeSort(nums, l, mid);      // Left Half
    mergeSort(nums, mid + 1, r);  // Right Half
 
    merge(nums, l, mid, r);
}

Time Complexity

ComponentTime
Merge Sorted Array FunO(n)
Recursion StackO(log n) as getting split in half recursively

Space Complexity

ComponentSpace
Temporary Array in Merge FunO(n)
Recursion StackO(log n)

Questions

Count Inversions (GFG) Find All nums[i] > nums[j] for i<j

// GFG - Count Inversions
 
class Solution {
public:
    int InvCount = 0;
 
    void countInversion(vector<int> &nums, int l1, int r1, int l2, int r2) {
        while (l1 <= r1 && l2 <= r2) {
            if (nums[l1] > nums[l2]) {
                InvCount += (r1 - l1 + 1);
                l2++;
            } else {
                l1++;
            }
        }
    }
 
    void merge(vector<int>& nums, int l, int mid, int r) {
        vector<int> sorted;
 
        int l1 = l, r1 = mid;
        int l2 = mid + 1, r2 = r;
 
        countInversion(nums, l1, r1, l2, r2);
 
        while (l1 <= r1 && l2 <= r2) {
            nums[l1] <= nums[l2]
                ? sorted.push_back(nums[l1++])
                : sorted.push_back(nums[l2++]);
        }
 
        while (l1 <= r1)
            sorted.push_back(nums[l1++]);
 
        while (l2 <= r2)
            sorted.push_back(nums[l2++]);
 
        for (auto &ele : sorted)
            nums[l++] = ele;
    }
 
    void mergeSort(vector<int>& nums, int l, int r) {
        if (l >= r)
            return;
 
        int mid = l + (r - l) / 2;
 
        mergeSort(nums, l, mid);
        mergeSort(nums, mid + 1, r);
 
        merge(nums, l, mid, r);
    }
 
    int inversionCount(vector<int>& nums) {
        mergeSort(nums, 0, nums.size() - 1);
        return InvCount;
    }
};

Reverse Pairs (LeetCode 493) Find All nums[i] > 2 * nums[j] for i<j`

// LeetCode 493 - Reverse Pairs
 
class Solution {
public:
    int InvCount = 0;
 
    void countInversion(vector<int> &nums, int l1, int r1, int l2, int r2) {
        while (l1 <= r1 && l2 <= r2) {
            if ((long long)nums[l1] > 2 * (long long)nums[l2]) {
                InvCount += (r1 - l1 + 1);
                l2++;
            } else {
                l1++;
            }
        }
    }
 
    void merge(vector<int>& nums, int l, int mid, int r) {
        vector<int> sorted;
 
        int l1 = l, r1 = mid;
        int l2 = mid + 1, r2 = r;
 
        countInversion(nums, l1, r1, l2, r2);
 
        while (l1 <= r1 && l2 <= r2) {
            nums[l1] <= nums[l2]
                ? sorted.push_back(nums[l1++])
                : sorted.push_back(nums[l2++]); // we can remove countInversion & add  InvCount+=(r1-l1+1) here for optimization to avoid extra n loop
        }
 
        while (l1 <= r1)
            sorted.push_back(nums[l1++]);
 
        while (l2 <= r2)
            sorted.push_back(nums[l2++]);
 
        for (auto &ele : sorted)
            nums[l++] = ele;
    }
 
    void mergeSort(vector<int>& nums, int l, int r) {
        if (l >= r)
            return;
 
        int mid = l + (r - l) / 2;
 
        mergeSort(nums, l, mid);
        mergeSort(nums, mid + 1, r);
 
        merge(nums, l, mid, r);
    }
 
    int reversePairs(vector<int>& nums) {
        mergeSort(nums, 0, nums.size() - 1);
        return InvCount;
    }
};

Quick Sort

šŸ“Œ Key Idea: Pick a pivot element, place it in its correct sorted position, then recursively sort the left and right partitions.

int partition(vector<int>& nums, int l, int r)
{
    int pivot = nums[r];
    int pIndex = l;
 
    for (int i = l; i < r; i++)
    {
        if (nums[i] < pivot)
        {
            swap(nums[pIndex], nums[i]);
            pIndex++;
        }
    }
 
    swap(nums[pIndex], nums[r]);
 
    return pIndex;
}
 
void quickSort(vector<int>& nums, int l, int r)
{
    if (l >= r)
        return;
 
    int p = partition(nums, l, r);
 
    quickSort(nums, l, p - 1);
    quickSort(nums, p + 1, r);
}
nums = [4, 2, 8, 1, 7, 3]
 
Pivot = 3
 
After partition:
 
[2, 1] | 3 | [8, 7, 4]
 
Pivot Index = 2

Time Complexity

CaseTime Complexity
Best CaseO(n log n)
Worst CaseO(n²)
Space Complexity
ComponentSpace
Recursion Stack (Average)O(log n)
Recursion Stack (Worst)O(n)

šŸ“Œ Interview Tip:

AlgorithmAverageWorstSpace
Merge SortO(n log n)O(n log n)O(n)
Quick SortO(n log n)O(n²)O(log n)

Comparison of Basic Sorting Algorithms

PropertyBubble SortSelection SortInsertion SortMerge SortQuick Sort
Best TimeO(n)O(n²)O(n)O(n log n)O(n log n)
Average TimeO(n²)O(n²)O(n²)O(n log n)O(n log n)
Worst TimeO(n²)O(n²)O(n²)O(n log n)O(n²)
Space ComplexityO(1)O(1)O(1)O(n) + O(log n)O(log n)
Stableāœ…āŒāœ…āœ…āŒ
In-Placeāœ…āœ…āœ…āŒāœ…
Best ForSmall / Nearly SortedMinimizing SwapsNearly Sorted ArraysGuaranteed PerformanceFast Practical Sorting

In-Place:- It modifies the original array directly instead of creating another array.

If Asked…Choose
Small, nearly sorted arrayInsertion Sort
Need stable sortingMerge Sort / Insertion Sort
Need guaranteed O(n log n)Merge Sort
Need in-place O(n log n) averageQuick Sort
Minimum swaps requiredSelection Sort
Best practical performanceQuick Sort
Linked List SortingMerge Sort
External Sorting (Huge Files)Merge Sort

C++ STL std::sort() uses Introsort, a hybrid of Quick Sort + Heap Sort + Insertion Sort. It provides O(n log n) worst-case time complexity while maintaining excellent practical performance.