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

Three classic algorithms that don't fit neatly under any one earlier phase, each solving one specific, common problem well. Quickselect finds the Kth smallest or largest element without fully sorting anything β€” it's Sorting Intuition's Quick Sort, minus the half of the work Quick Sort does that Quickselect doesn't need. KMP (Knuth-Morris-Pratt) finds a pattern inside a larger text in O(n+m) time instead of the naive O(nΓ—m), by never re-checking characters 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 β€” the classic real-world application of a heap, direct from Phase 5.

What ties them together isn't a shared data structure β€” it's that each one takes an algorithm already built earlier in this curriculum and asks a sharper, narrower question of it: Quick Sort's partition, but only chasing one answer instead of a full order; a heap's pop-and-push cycle, but merging instead of just extracting.

Build It
Three techniques, no shared structure
# Quickselect reuses Quick Sort's partition() from Sorting Intuition, unchanged.
# KMP builds one small helper array (the "failure function") before searching.
# 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)    // Sorting Intuition's partition(), unchanged
        if pivotIndex == k:
            return arr[pivotIndex]
        elif pivotIndex < k:
            // answer is in the right piece only β€” left piece is done, discard it
            lo = pivotIndex + 1
        else:
            // answer is in the left piece only β€” 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 only needs one position, so after partitioning it keeps only the side that could contain index k and throws the other side away entirely β€” no recursive call into it at all. Discarding roughly half the remaining work at every step, instead of keeping 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] = length of the longest prefix of pattern that's also a suffix ending at i
    lps = array of zeros, length = pattern.length
    length = 0                                        // 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 β€” don't restart at 0
        else:
            lps[i] = 0
            i += 1
    return lps

function kmpSearch(text, pattern):
    lps = buildFailureFunction(pattern)
    i, j = 0, 0                            // i walks text, j walks pattern
    while i < text.length:
        if text[i] == pattern[j]:
            i += 1
            j += 1
            if j == pattern.length:
                return i - j                // found β€” starting index of the match
        elif j > 0:
            // mismatch: skip ahead using what's already matched β€” i never moves backward
            j = lps[j - 1]
        else:
            i += 1
    return -1                                // pattern never found
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 was already matched tells you where it's safe to resume, instead of throwing all of that work away and restarting from scratch one character over. The naive approach re-checks characters of text it has already looked at, over and over; KMP's i pointer only ever moves forward, never backward β€” that's 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
    // Phase 5's 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                     // two least-frequent nodes...
        right = pop minimum from minHeap
        // ...merged into one, 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 encoding tree's root
Every character's binary code is the path from the root to its leaf, 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) β€” the same 'always take the current smallest' shape as Heaps & Priority Queue's pop/push cycle, just applied to build a tree instead of extract a sequence.
Know It

Quickselect averages O(n) β€” same average-case reasoning as Quick Sort's partition, but summed over a shrinking single side instead of two recursive halves. KMP is O(n+m): O(m) to build the failure function once, O(n) to scan the text once, each character of text visited a bounded number of times thanks to the fallback (never restarting from scratch). Huffman Coding is O(n log n): n-1 merges, each merge doing 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)Same pivot-choice risk as Quick Sort: a consistently bad pivot (already-sorted input, always picking the last element) shrinks the search space by only one element per step instead of roughly half.
KMP failure function (build)O(m)O(m)One pass over the pattern; the fallback (length = lps[length - 1]) can move length backward, but it can only do that as many times total as it moved forward, keeping the whole pass linear.
KMP search (scan)O(n)O(1) extrai only ever moves forward through text; j can fall back via lps but never causes i to re-visit a character of text it already passed.
Huffman Coding (build the tree)O(n log n)O(n)n-1 merges to collapse n leaf nodes into one tree, each merge costing 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 pivot

Identical failure mode to Quick Sort, for the identical reason: a predictably bad pivot choice puts zero elements on one side of every partition, shrinking the remaining search space by exactly one element per step instead of roughly half. The fix is the same fix too β€” pick a random pivot, or the median of a few sampled elements, so an adversarial or already-sorted input can't reliably trigger the worst case.

A subtly wrong failure function

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

This is genuinely one of the trickier algorithms in the curriculum to get exactly right, and it's worth an honest warning rather than false confidence: a failure function built even slightly wrong doesn't usually crash, it just quietly produces a table that causes kmpSearch to skip past a real match or miss it outright. Trace buildFailureFunction() by hand on a small self-overlapping pattern (like "aabaaab") before trusting 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 merge loop's condition (while minHeap.size > 1) never runs, because the heap only ever had one element β€” the loop exits immediately and buildHuffmanTree returns a single leaf node with no merges performed. That leaf has no left/right children, so the usual 'path from root to leaf' encoding breaks down (a single node has no path at all). This degenerate case needs an explicit rule handled outside the general algorithm β€” commonly, assign that one character a 1-bit code (like "0") by convention, rather than trying 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 doesn't break correctness β€” every valid Huffman tree built from the same frequencies produces an optimal (minimum total encoded length) result, regardless of which same-frequency node the heap happens to pop first on a tie. But it does mean two runs, or two different heap implementations, can produce two different β€” and both entirely valid β€” encodings for the identical input. Worth naming explicitly so a different-but-correct output on a re-run doesn't get mistaken for a bug.

Use It
Kth Largest Element in an Array
Third revisit of this exact problem: full sort in Sorting Intuition, a size-k min-heap in Heaps & Priority Queue, and now quickSelect() from Build It β€” three genuinely different techniques, same question, worth comparing side by side.
Medium
K Closest Points to Origin
Revisit from Heaps & Priority Queue's max-heap approach β€” quickSelect() on squared distance instead, partitioning until the k closest points settle into the first k positions.
Medium
Find the Index of the First Occurrence in a String
kmpSearch() from Build It, verbatim β€” the classic KMP application. Honest note: LeetCode's constraints here are small enough that a naive O(nΓ—m) brute force also passes; implement KMP anyway, since the teaching point is the technique, not squeezing past this specific input size.
Easy
Repeated String Match
Build the smallest repetition of a that could possibly contain b by length alone, then run kmpSearch() (b as the pattern) against that repeated string plus one extra copy, to catch a match straddling the repetition boundary.
Medium
Top K Frequent Words
Count frequencies (Hashing), then either a heap keyed on (frequency, word) with a custom tie-break comparator, or quickSelect() over the same key β€” both valid, heap is simpler here since output must stay sorted.
Medium
Third Maximum Number
A tiny, fixed k (k=3) makes quickSelect() overkill β€” but it's 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