LearnAbout

Misc Algorithms

On this page
Quick reference
QuickselectO(n) average, O(n²) worst case
KMP failure function (build)O(m)
KMP search (scan)O(n)
Huffman Coding (build the tree)O(n log n)
The Idea

Here are three classic algorithms that do not fit neatly under any one earlier phase. Each one solves a specific, common problem well. Quickselect finds the Kth smallest or largest element without a full sort of anything. It is the Quick Sort from Sorting Intuition, minus the half of the work that Quick Sort does and Quickselect does not need. KMP (Knuth-Morris-Pratt) finds a pattern inside a larger text in O(n+m) time instead of the naive O(n×m), because it never checks characters again that it has already confirmed. Huffman Coding builds the most space-efficient variable-length binary encoding for a set of characters, given how often each one appears. It is the classic real-world application of a heap, direct from Phase 5.

What ties them together is not a shared data structure. It is that each one takes an algorithm already built earlier in this curriculum and asks a sharper, narrower question of it: the partition of Quick Sort, but with a chase of only one answer instead of a full order; the pop-and-push cycle of a heap, but with a merge instead of only an extract.

Build It
Three techniques, no shared structure
# Quickselect reuses the partition() of Quick Sort from Sorting Intuition, unchanged.
# KMP builds one small helper array (the "failure function") before the search.
# Huffman Coding reuses the min-heap from Heaps & Priority Queue, keyed by frequency.
Quickselect — Kth smallest via one-sided partitioning — O(n) average
function quickSelect(arr, k):             // find the element that belongs at index k once sorted
    lo, hi = 0, arr.length - 1
    while true:
        pivotIndex = partition(arr, lo, hi)    // the partition() of Sorting Intuition, unchanged
        if pivotIndex == k:
            return arr[pivotIndex]
        elif pivotIndex < k:
            // the answer is in the right piece only: the left piece is done, discard it
            lo = pivotIndex + 1
        else:
            // the answer is in the left piece only: the right piece is done, discard it
            hi = pivotIndex - 1
Quick Sort recurses into both sides of every partition, because it needs the whole array sorted. Quickselect needs only one position. So after the partition, it keeps only the side that could contain index k and throws the other side away, with no recursive call into it. To discard about half of the remaining work at every step, instead of a keep of all of it, is exactly why Quickselect averages O(n) where Quick Sort averages O(n log n).
KMP — build the failure function, then search — O(n+m)
function buildFailureFunction(pattern):
    // lps[i] = the length of the longest prefix of pattern that is also a suffix ending at i
    lps = array of zeros, length = pattern.length
    length = 0                                        // the length of the matched prefix so far
    i = 1
    while i < pattern.length:
        if pattern[i] == pattern[length]:
            length += 1
            lps[i] = length
            i += 1
        elif length > 0:
            length = lps[length - 1]      // fall back to the next-best prefix, do not restart at 0
        else:
            lps[i] = 0
            i += 1
    return lps

function kmpSearch(text, pattern):
    lps = buildFailureFunction(pattern)
    i, j = 0, 0                            // i walks the text, j walks the pattern
    while i < text.length:
        if text[i] == pattern[j]:
            i += 1
            j += 1
            if j == pattern.length:
                return i - j                // found: the start index of the match
        elif j > 0:
            // mismatch: skip ahead with what is already matched; i never moves backward
            j = lps[j - 1]
        else:
            i += 1
    return -1                                // pattern never found
Here is the plain-language version, worth holding onto even where the table-building code feels dense. When a mismatch happens partway through a match, some of what is already matched tells you where it is safe to resume, instead of a throw of all that work and a restart from the start one character over. The naive approach checks characters of the text again that it has already looked at, over and over. The i pointer of KMP moves only forward, never backward. That is the whole source of the O(n+m) bound.
Huffman Coding — repeatedly merge the two least-frequent nodes — O(n log n)
structure HuffmanNode:
    char           // the character, or NULL for a merged internal node
    freq
    left, right    // -> another HuffmanNode, or NULL

function buildHuffmanTree(charFrequencies):        // list of (char, freq) pairs
    // the Phase 5 heap, keyed by frequency instead of value
    minHeap = a min-heap of HuffmanNode, ordered by freq
    for (char, freq) in charFrequencies:
        push HuffmanNode(char, freq, NULL, NULL) onto minHeap
    while minHeap.size > 1:
        left = pop minimum from minHeap                     // the two least-frequent nodes...
        right = pop minimum from minHeap
        // ...merged into one, with the frequency summed
        merged = HuffmanNode(NULL, left.freq + right.freq, left, right)
        push merged onto minHeap
    return pop minimum from minHeap    // the one node left is the root of the encoding tree
The binary code of each character is the path from the root to its leaf, where left = 0 and right = 1. Because the two least-frequent nodes always merge first, frequent characters end up shallow (short codes) and rare characters end up deep (long codes). This is the same "always take the current smallest" shape as the pop/push cycle in Heaps & Priority Queue, applied to build a tree instead of extract a sequence.
Know It

Quickselect averages O(n): the same average-case reasoning as the partition of Quick Sort, but summed over a shrinking single side instead of two recursive halves. KMP is O(n+m): O(m) to build the failure function one time, and O(n) to scan the text one time. Each character of the text is visited a bounded number of times, thanks to the fallback (no restart from the start). Huffman Coding is O(n log n): there are n-1 merges, and each merge does two pops and one push on a heap of up to n elements, and each of those heap operations costs O(log n).

OperationTimeSpaceWhy
QuickselectO(n) average, O(n²) worst caseO(1) extra (in place)This is the same pivot-choice risk as Quick Sort. A bad pivot (an already-sorted input, with a pick of the last element every time) shrinks the search space by only one element per step instead of about half.
KMP failure function (build)O(m)O(m)This is one pass over the pattern. The fallback (length = lps[length - 1]) can move length backward, but it can do that only as many times in total as it moved forward. So the whole pass stays linear.
KMP search (scan)O(n)O(1) extrai moves only forward through the text. j can fall back through lps, but it never makes i visit a character of the text that it already passed.
Huffman Coding (build the tree)O(n log n)O(n)There are n-1 merges to collapse n leaf nodes into one tree. Each merge costs O(log n) for its two pops and one push.
Break It

Quickselect degrading to O(n²) on a bad pivot

the array is already sorted (or reverse-sorted) and partition() always picks the last element as the pivot

This is the identical failure mode as Quick Sort, for the identical reason. A predictably bad pivot choice puts zero elements on one side of every partition. This shrinks the remaining search space by exactly one element per step instead of about half. The fix is the same fix: pick a random pivot, or the median of a few sample elements, so an adversarial or already-sorted input cannot reliably trigger the worst case.

A subtly wrong failure function

the fallback line (length = lps[length - 1]) is wrong, or the base case for length == 0 is mishandled

This is genuinely one of the harder algorithms in the curriculum to get exactly right, and it is worth an honest warning, not false confidence. A failure function built even a little wrong usually does not crash. It quietly produces a table that makes kmpSearch skip past a real match or miss it. Trace buildFailureFunction() by hand on a small self-overlapping pattern (like "aabaaab") before you trust an implementation. The self-overlap is exactly what a broken failure function gets wrong first.

Huffman Coding with only one distinct character

charFrequencies has exactly one (char, freq) entry

The condition of the merge loop (while minHeap.size > 1) never runs, because the heap had only one element. The loop exits immediately, and buildHuffmanTree returns a single leaf node with no merges done. That leaf has no left or right children, so the usual "path from root to leaf" encoding does not work (a single node has no path at all). This degenerate case needs an explicit rule, handled outside the general algorithm. Usually, give that one character a 1-bit code (such as "0") by convention, instead of a try to force the tree-walk logic to produce one.

Ties in frequency resolved inconsistently

two or more characters (or merged nodes) in the heap share the exact same frequency

This does not break correctness. Every valid Huffman tree built from the same frequencies produces an optimal (minimum total encoded length) result, whichever same-frequency node the heap pops first on a tie. But it does mean that two runs, or two different heap implementations, can produce two different (and both valid) encodings for the identical input. This is worth naming explicitly, so a different-but-correct output on a re-run is not mistaken for a bug.

Use It
Kth Largest Element in an Array
This is the third time you see this exact problem: a full sort in Sorting Intuition, a size-k min-heap in Heaps & Priority Queue, and now quickSelect() from Build It. These are three genuinely different techniques for the same question, worth a side-by-side comparison.
Medium
K Closest Points to Origin
Review this from the max-heap approach in Heaps & Priority Queue. Use quickSelect() on the squared distance instead. Partition until the k closest points settle into the first k positions.
Medium
Find the Index of the First Occurrence in a String
Use kmpSearch() from Build It, exactly. This is the classic KMP application. Honest note: the LeetCode constraints here are small enough that a naive O(n×m) brute force also passes. Implement KMP anyway, because the teaching point is the technique, not a squeeze past this specific input size.
Easy
Repeated String Match
Build the smallest repetition of a that could contain b by length alone. Then run kmpSearch() (with b as the pattern) against that repeated string plus one extra copy, to catch a match that straddles the repetition boundary.
Medium
Top K Frequent Words
Count the frequencies (Hashing). Then use either a heap keyed on (frequency, word) with a custom tie-break comparator, or quickSelect() over the same key. Both are valid. The heap is simpler here, because the output must stay sorted.
Medium
Third Maximum Number
A tiny, fixed k (k=3) makes quickSelect() more than you need. But it is the same underlying question ("the Kth largest, without a full sort") at the smallest possible scale, solvable with three running variables in one pass instead.
Easy