Skip to main content

String Algorithms

Published 19 min readMarkdown
String Algorithms

Sorting Algorithms

KMP (Knuth–Morris–Pratt) Algorithm

Core idea

Build a table of how far to "jump back" in the pattern when a mismatch happens, using the pattern's internal prefix-suffix structure. Then scan the text once, reusing previous partial matches so no character is rechecked unnecessarily.

Optimizations / notes

  • LPS (longest proper prefix that is also suffix) array is the key.

  • KMP is optimal for exact single-pattern search on static text.

  • For multiple patterns use Aho–Corasick instead.

Time complexity : O(n + m) where n = len(text), m = len(pattern)
Space complexity : O(m) for the LPS array

Python Code

def compute_lps(pattern):
    """Compute LPS (longest proper prefix which is also suffix) array."""
    m = len(pattern)
    lps = [0] * m
    length = 0  # length of the previous longest prefix suffix
    i = 1
    # Build lps[1..m-1]
    while i < m:
        if pattern[i] == pattern[length]:
            length += 1
            lps[i] = length
            i += 1
        else:
            if length != 0:
                # fallback to previous longest prefix suffix
                length = lps[length - 1]
                # do not increment i here
            else:
                lps[i] = 0
                i += 1
    return lps

def kmp_search(text, pattern):
    """Return list of starting indices where pattern is found in text."""
    n, m = len(text), len(pattern)
    if m == 0:
        return list(range(n + 1))  # empty pattern matches at all positions
    lps = compute_lps(pattern)
    res = []
    i = j = 0  # i -> text, j -> pattern
    while i < n:
        if text[i] == pattern[j]:
            i += 1
            j += 1
            if j == m:
                res.append(i - j)
                j = lps[j - 1]  # continue searching
        else:
            if j != 0:
                j = lps[j - 1]
            else:
                i += 1
    return res

def main():
    # Example 1
    text1 = "ABABDABACDABABCABAB"
    pattern1 = "ABABCABAB"
    print("KMP Example 1")
    print("Text:", text1)
    print("Pattern:", pattern1)
    print("Matches at indices:", kmp_search(text1, pattern1))
    print()

    # Example 2
    text2 = "aaaaa"
    pattern2 = "aa"
    print("KMP Example 2")
    print("Text:", text2)
    print("Pattern:", pattern2)
    print("Matches at indices:", kmp_search(text2, pattern2))

if __name__ == "__main__":
    main()

Java Code

public class KMPExample {

    // Compute LPS array
    public static int[] computeLPS(String pat) {
        int m = pat.length();
        int[] lps = new int[m];
        int len = 0;
        int i = 1;
        while (i < m) {
            if (pat.charAt(i) == pat.charAt(len)) {
                len++;
                lps[i] = len;
                i++;
            } else {
                if (len != 0) {
                    len = lps[len - 1];
                } else {
                    lps[i] = 0;
                    i++;
                }
            }
        }
        return lps;
    }

    // KMP search
    public static java.util.List<Integer> kmpSearch(String text, String pat) {
        java.util.List<Integer> res = new java.util.ArrayList<>();
        int n = text.length(), m = pat.length();
        if (m == 0) {
            for (int i = 0; i <= n; i++) res.add(i);
            return res;
        }
        int[] lps = computeLPS(pat);
        int i = 0, j = 0;
        while (i < n) {
            if (text.charAt(i) == pat.charAt(j)) {
                i++; j++;
                if (j == m) {
                    res.add(i - j);
                    j = lps[j - 1];
                }
            } else {
                if (j != 0) j = lps[j - 1];
                else i++;
            }
        }
        return res;
    }

    public static void main(String[] args) {
        // Example 1
        String text1 = "ABABDABACDABABCABAB";
        String pat1 = "ABABCABAB";
        System.out.println("KMP Example 1");
        System.out.println("Text: " + text1);
        System.out.println("Pattern: " + pat1);
        System.out.println("Matches at indices: " + kmpSearch(text1, pat1));
        System.out.println();

        // Example 2
        String text2 = "aaaaa";
        String pat2 = "aa";
        System.out.println("KMP Example 2");
        System.out.println("Text: " + text2);
        System.out.println("Pattern: " + pat2);
        System.out.println("Matches at indices: " + kmpSearch(text2, pat2));
    }
}

Rabin–Karp Algorithm

Core idea

Compute a rolling numeric hash for the pattern and for each text window of the same length. Compare hashes quickly. When a hash matches, confirm by direct comparison to avoid false positives from hash collisions.

Optimizations / notes

  • Use a good base and a large modulus to reduce collisions.

  • For long patterns or many patterns, combine with multiple moduli or use double hashing.

  • Rabin–Karp is especially useful for multi-pattern search and plagiarism detection.

Time complexity : Average O(n + m). Worst-case O(nm) if many spurious hash collisions and naive verification costs dominate. With good hashing collisions are rare. Space complexity : O(1) (excluding input/output) or O(m) if storing patterns.

Python Code

def rabin_karp_search(text, pattern, base=256, mod=10**9+7):
    """Return list of starting indices where pattern occurs in text."""
    n, m = len(text), len(pattern)
    if m == 0:
        return list(range(n + 1))
    if m > n:
        return []
    # precompute base^(m-1) % mod
    power = pow(base, m-1, mod)
    # initial hash for pattern and first window
    pat_hash = 0
    win_hash = 0
    for i in range(m):
        pat_hash = (pat_hash * base + ord(pattern[i])) % mod
        win_hash = (win_hash * base + ord(text[i])) % mod
    res = []
    for i in range(n - m + 1):
        if pat_hash == win_hash:
            # potential match, verify to avoid false positive
            if text[i:i+m] == pattern:
                res.append(i)
        if i < n - m:
            # roll: remove leading char, add next char
            leading = (ord(text[i]) * power) % mod
            win_hash = (win_hash - leading) % mod
            win_hash = (win_hash * base + ord(text[i + m])) % mod
            # ensure positive
            win_hash = (win_hash + mod) % mod
    return res

def main():
    # Example 1
    text1 = "the quick brown fox jumps over the lazy dog"
    pattern1 = "quick"
    print("Rabin-Karp Example 1")
    print("Text:", text1)
    print("Pattern:", pattern1)
    print("Matches at indices:", rabin_karp_search(text1, pattern1))
    print()

    # Example 2 (multiple occurrences)
    text2 = "abcdabcabcd"
    pattern2 = "abc"
    print("Rabin-Karp Example 2")
    print("Text:", text2)
    print("Pattern:", pattern2)
    print("Matches at indices:", rabin_karp_search(text2, pattern2))

if __name__ == "__main__":
    main()

Java Code

import java.util.*;

public class RabinKarpExample {

    public static List<Integer> rabinKarpSearch(String text, String pat, int base, long mod) {
        int n = text.length(), m = pat.length();
        List<Integer> res = new ArrayList<>();
        if (m == 0) {
            for (int i = 0; i <= n; i++) res.add(i);
            return res;
        }
        if (m > n) return res;
        long power = 1;
        for (int i = 0; i < m - 1; i++) power = (power * base) % mod;
        long patHash = 0, winHash = 0;
        for (int i = 0; i < m; i++) {
            patHash = (patHash * base + pat.charAt(i)) % mod;
            winHash = (winHash * base + text.charAt(i)) % mod;
        }
        for (int i = 0; i <= n - m; i++) {
            if (patHash == winHash) {
                if (text.substring(i, i + m).equals(pat)) res.add(i);
            }
            if (i < n - m) {
                long leading = (text.charAt(i) * power) % mod;
                winHash = (winHash - leading + mod) % mod;
                winHash = (winHash * base + text.charAt(i + m)) % mod;
            }
        }
        return res;
    }

    public static void main(String[] args) {
        // Example 1
        String text1 = "the quick brown fox jumps over the lazy dog";
        String pat1 = "quick";
        System.out.println("Rabin-Karp Example 1");
        System.out.println("Text: " + text1);
        System.out.println("Pattern: " + pat1);
        System.out.println("Matches at indices: " + rabinKarpSearch(text1, pat1, 256, 1000000007L));
        System.out.println();

        // Example 2
        String text2 = "abcdabcabcd";
        String pat2 = "abc";
        System.out.println("Rabin-Karp Example 2");
        System.out.println("Text: " + text2);
        System.out.println("Pattern: " + pat2);
        System.out.println("Matches at indices: " + rabinKarpSearch(text2, pat2, 256, 1000000007L));
    }
}

Boyer–Moore Algorithm

Core idea

Compare pattern from right to left and skip ahead smartly on mismatch using precomputed heuristics. Two common heuristics:

  • Bad-character rule: align pattern to skip past the mismatched character.

  • Good-suffix rule: align based on matching suffix that was found.

Optimizations / notes

  • Full Boyer–Moore includes both heuristics and is very fast in practice for typical texts.

  • Implementation below uses both heuristics in a compact form. Good-suffix preprocessing is more intricate but included for correctness.

Time complexity : Best O(n / m) average with large shifts. Worst-case O(n + m) for modern implementations with both heuristics (amortized linear). Some textbook analyses show worst-case O(nm) for naive BM if only bad-character used; combining heuristics fixes that. Space complexity : O(m + alphabet) (tables for bad char and good suffix).

Python Code

def build_bad_char_table(pattern):
    """Bad character table mapping char -> last index in pattern."""
    table = {}
    for i, ch in enumerate(pattern):
        table[ch] = i
    return table

def build_good_suffix_table(pattern):
    """
    Build good suffix shift table (also called full shift).
    This implementation computes two arrays: suffixes and shift.
    """
    m = len(pattern)
    shift = [m] * (m + 1)
    suff = [0] * (m + 1)
    # Compute suffixes
    suff[m - 1] = m
    g = m - 1
    f = 0
    for i in range(m - 2, -1, -1):
        if i > g and suff[i + m - 1 - f] < i - g:
            suff[i] = suff[i + m - 1 - f]
        else:
            if i < g:
                g = i
            f = i
            while g >= 0 and pattern[g] == pattern[g + m - 1 - f]:
                g -= 1
            suff[i] = f - g
    # Build shift
    for i in range(m + 1):
        shift[i] = m
    j = 0
    for i in range(m - 1, -1, -1):
        if suff[i] == i + 1:
            while j < m - 1 - i:
                if shift[j] == m:
                    shift[j] = m - 1 - i
                j += 1
    for i in range(m - 1):
        shift[m - 1 - suff[i]] = m - 1 - i
    return shift

def boyer_moore_search(text, pattern):
    """Return list of starting indices of pattern in text using Boyer-Moore."""
    n, m = len(text), len(pattern)
    if m == 0:
        return list(range(n + 1))
    bad = build_bad_char_table(pattern)
    good = build_good_suffix_table(pattern)
    res = []
    s = 0
    while s <= n - m:
        j = m - 1
        while j >= 0 and pattern[j] == text[s + j]:
            j -= 1
        if j < 0:
            res.append(s)
            s += good[0]  # shift so next possible match
        else:
            bc_shift = j - bad.get(text[s + j], -1)
            gs_shift = good[j]
            s += max(1, bc_shift, gs_shift)
    return res

def main():
    # Example 1
    text1 = "HERE IS A SIMPLE EXAMPLE"
    pattern1 = "EXAMPLE"
    print("Boyer-Moore Example 1")
    print("Text:", text1)
    print("Pattern:", pattern1)
    print("Matches at indices:", boyer_moore_search(text1, pattern1))
    print()

    # Example 2 (multiple and overlapping)
    text2 = "ABAAABCD"
    pattern2 = "ABA"
    print("Boyer-Moore Example 2")
    print("Text:", text2)
    print("Pattern:", pattern2)
    print("Matches at indices:", boyer_moore_search(text2, pattern2))

if __name__ == "__main__":
    main()

Java Code

import java.util.*;

public class BoyerMooreExample {

    // Build bad character table
    public static Map<Character, Integer> buildBadCharTable(String pat) {
        Map<Character, Integer> table = new HashMap<>();
        for (int i = 0; i < pat.length(); i++) {
            table.put(pat.charAt(i), i);
        }
        return table;
    }

    // Build good suffix shift table
    public static int[] buildGoodSuffixTable(String pat) {
        int m = pat.length();
        int[] shift = new int[m + 1];
        int[] suff = new int[m + 1];
        // compute suff
        suff[m - 1] = m;
        int g = m - 1;
        int f = 0;
        for (int i = m - 2; i >= 0; i--) {
            if (i > g && suff[i + m - 1 - f] < i - g) {
                suff[i] = suff[i + m - 1 - f];
            } else {
                if (i < g) g = i;
                f = i;
                while (g >= 0 && pat.charAt(g) == pat.charAt(g + m - 1 - f)) g--;
                suff[i] = f - g;
            }
        }
        Arrays.fill(shift, m);
        int j = 0;
        for (int i = m - 1; i >= 0; i--) {
            if (suff[i] == i + 1) {
                while (j < m - 1 - i) {
                    if (shift[j] == m) shift[j] = m - 1 - i;
                    j++;
                }
            }
        }
        for (int i = 0; i < m - 1; i++) {
            shift[m - 1 - suff[i]] = m - 1 - i;
        }
        return shift;
    }

    public static List<Integer> boyerMooreSearch(String text, String pat) {
        int n = text.length(), m = pat.length();
        List<Integer> res = new ArrayList<>();
        if (m == 0) {
            for (int i = 0; i <= n; i++) res.add(i);
            return res;
        }
        Map<Character, Integer> bad = buildBadCharTable(pat);
        int[] good = buildGoodSuffixTable(pat);
        int s = 0;
        while (s <= n - m) {
            int j = m - 1;
            while (j >= 0 && pat.charAt(j) == text.charAt(s + j)) j--;
            if (j < 0) {
                res.add(s);
                s += good[0];
            } else {
                int bcShift = j - bad.getOrDefault(text.charAt(s + j), -1);
                int gsShift = good[j];
                s += Math.max(1, Math.max(bcShift, gsShift));
            }
        }
        return res;
    }

    public static void main(String[] args) {
        // Example 1
        String text1 = "HERE IS A SIMPLE EXAMPLE";
        String pat1 = "EXAMPLE";
        System.out.println("Boyer-Moore Example 1");
        System.out.println("Text: " + text1);
        System.out.println("Pattern: " + pat1);
        System.out.println("Matches at indices: " + boyerMooreSearch(text1, pat1));
        System.out.println();

        // Example 2
        String text2 = "ABAAABCD";
        String pat2 = "ABA";
        System.out.println("Boyer-Moore Example 2");
        System.out.println("Text: " + text2);
        System.out.println("Pattern: " + pat2);
        System.out.println("Matches at indices: " + boyerMooreSearch(text2, pat2));
    }
}

Z Algorithm

Core idea

Compute for each position in a string the length of the longest substring starting there that matches the prefix. For pattern search, form string = pattern + '$' + text and compute Z array. Wherever a Z value equals pattern length is a match.

Optimizations / notes

  • Linear time via maintaining a [L, R] window of the rightmost segment matched to prefix.

  • Z algorithm is very fast for many pattern-processing tasks, e.g., computing runs, borders, or multiple pattern searches via concatenation.

Time complexity : O(n + m) when used for pattern search (linear overall)
Space complexity : O(n + m) for concatenated string and Z array (or O(n) for Z alone)

Python Code

def compute_z(s):
    """Compute Z array for string s in O(len(s))."""
    n = len(s)
    z = [0] * n
    l = r = 0
    for i in range(1, n):
        if i <= r:
            z[i] = min(r - i + 1, z[i - l])
        while i + z[i] < n and s[z[i]] == s[i + z[i]]:
            z[i] += 1
        if i + z[i] - 1 > r:
            l, r = i, i + z[i] - 1
    z[0] = n
    return z

def z_search(text, pattern):
    """Return list of starting indices where pattern found using Z algorithm."""
    if len(pattern) == 0:
        return list(range(len(text) + 1))
    concat = pattern + "$" + text
    z = compute_z(concat)
    res = []
    m = len(pattern)
    for i in range(m + 1, len(concat)):
        if z[i] == m:
            res.append(i - m - 1)  # index in text
    return res

def main():
    # Example 1
    text1 = "abxabcabcaby"
    pattern1 = "abcaby"
    print("Z-Algorithm Example 1")
    print("Text:", text1)
    print("Pattern:", pattern1)
    print("Matches at indices:", z_search(text1, pattern1))
    print()

    # Example 2
    text2 = "aaaaa"
    pattern2 = "aa"
    print("Z-Algorithm Example 2")
    print("Text:", text2)
    print("Pattern:", pattern2)
    print("Matches at indices:", z_search(text2, pattern2))

if __name__ == "__main__":
    main()

Java Code

import java.util.*;

public class ZAlgorithmExample {

    public static int[] computeZ(String s) {
        int n = s.length();
        int[] z = new int[n];
        int l = 0, r = 0;
        for (int i = 1; i < n; i++) {
            if (i <= r) z[i] = Math.min(r - i + 1, z[i - l]);
            while (i + z[i] < n && s.charAt(z[i]) == s.charAt(i + z[i])) z[i]++;
            if (i + z[i] - 1 > r) {
                l = i; r = i + z[i] - 1;
            }
        }
        z[0] = n;
        return z;
    }

    public static List<Integer> zSearch(String text, String pat) {
        List<Integer> res = new ArrayList<>();
        if (pat.length() == 0) {
            for (int i = 0; i <= text.length(); i++) res.add(i);
            return res;
        }
        String concat = pat + "$" + text;
        int[] z = computeZ(concat);
        int m = pat.length();
        for (int i = m + 1; i < concat.length(); i++) {
            if (z[i] == m) res.add(i - m - 1);
        }
        return res;
    }

    public static void main(String[] args) {
        // Example 1
        String text1 = "abxabcabcaby";
        String pat1 = "abcaby";
        System.out.println("Z-Algorithm Example 1");
        System.out.println("Text: " + text1);
        System.out.println("Pattern: " + pat1);
        System.out.println("Matches at indices: " + zSearch(text1, pat1));
        System.out.println();

        // Example 2
        String text2 = "aaaaa";
        String pat2 = "aa";
        System.out.println("Z-Algorithm Example 2");
        System.out.println("Text: " + text2);
        System.out.println("Pattern: " + pat2);
        System.out.println("Matches at indices: " + zSearch(text2, pat2));
    }
}

Aho–Corasick Algorithm (Multiple Pattern String Matching)

Core Idea

The Aho–Corasick algorithm efficiently finds all occurrences of multiple patterns in a text at once. It combines Trie + Failure Links (like KMP’s partial match table).

  • Trie step: insert all patterns.

  • Failure link step: connect each node to the longest proper suffix that is also a prefix.

  • Search step: process text in linear time using these links.

This achieves O(N + M + Z) time where

  • N = length of text,

  • M = total length of all patterns,

  • Z = number of matches.


Python Code

from collections import deque, defaultdict

class AhoCorasick:
    def __init__(self):
        self.trie = [{}]
        self.output = [set()]
        self.fail = [0]

    def insert(self, word):
        node = 0
        for ch in word:
            if ch not in self.trie[node]:
                self.trie[node][ch] = len(self.trie)
                self.trie.append({})
                self.output.append(set())
                self.fail.append(0)
            node = self.trie[node][ch]
        self.output[node].add(word)

    def build(self):
        q = deque()
        for ch, nxt in self.trie[0].items():
            q.append(nxt)
            self.fail[nxt] = 0

        while q:
            r = q.popleft()
            for ch, nxt in self.trie[r].items():
                q.append(nxt)
                f = self.fail[r]
                while f and ch not in self.trie[f]:
                    f = self.fail[f]
                self.fail[nxt] = self.trie[f].get(ch, 0)
                self.output[nxt] |= self.output[self.fail[nxt]]

    def search(self, text):
        node = 0
        result = []
        for i, ch in enumerate(text):
            while node and ch not in self.trie[node]:
                node = self.fail[node]
            node = self.trie[node].get(ch, 0)
            for pat in self.output[node]:
                result.append((i - len(pat) + 1, pat))
        return result

if __name__ == "__main__":
    ac = AhoCorasick()
    patterns = ["he", "she", "hers", "his"]
    for p in patterns:
        ac.insert(p)
    ac.build()
    text = "ushers"
    res = ac.search(text)
    print("Text:", text)
    print("Matches found:", res)

    # Second example
    ac2 = AhoCorasick()
    for p in ["ab", "bc", "abc"]:
        ac2.insert(p)
    ac2.build()
    print(ac2.search("abcbcabc"))

Java Code

import java.util.*;

class AhoCorasick {
    static class Node {
        Map<Character, Node> next = new HashMap<>();
        Node fail;
        List<String> out = new ArrayList<>();
    }

    private Node root = new Node();

    public void insert(String word) {
        Node cur = root;
        for (char ch : word.toCharArray()) {
            cur = cur.next.computeIfAbsent(ch, k -> new Node());
        }
        cur.out.add(word);
    }

    public void build() {
        Queue<Node> q = new ArrayDeque<>();
        for (Node node : root.next.values()) {
            node.fail = root;
            q.add(node);
        }
        while (!q.isEmpty()) {
            Node r = q.poll();
            for (var e : r.next.entrySet()) {
                char ch = e.getKey();
                Node nxt = e.getValue();
                q.add(nxt);
                Node f = r.fail;
                while (f != null && !f.next.containsKey(ch)) f = f.fail;
                nxt.fail = (f == null) ? root : f.next.get(ch);
                nxt.out.addAll(nxt.fail.out);
            }
        }
    }

    public List<String> search(String text) {
        List<String> res = new ArrayList<>();
        Node node = root;
        for (char ch : text.toCharArray()) {
            while (node != root && !node.next.containsKey(ch))
                node = node.fail;
            node = node.next.getOrDefault(ch, root);
            res.addAll(node.out);
        }
        return res;
    }

    public static void main(String[] args) {
        AhoCorasick ac = new AhoCorasick();
        for (String p : List.of("he", "she", "his", "hers")) ac.insert(p);
        ac.build();
        System.out.println(ac.search("ushers"));

        AhoCorasick ac2 = new AhoCorasick();
        for (String p : List.of("ab", "bc", "abc")) ac2.insert(p);
        ac2.build();
        System.out.println(ac2.search("abcbcabc"));
    }
}

Time Complexity: O(N + M + Z)
Space Complexity: O(M × alphabet size)


Manacher’s Algorithm (Longest Palindromic Substring)

Core Idea

Expands around every character in linear time by avoiding redundant comparisons.
It transforms the string (using # between letters) to handle even/odd cases uniformly, then expands palindromes using a mirror property around a center.


Python Code

def manacher(s):
    t = "#" + "#".join(s) + "#"
    n = len(t)
    P = [0]*n
    center = right = 0
    max_len = 0
    center_index = 0

    for i in range(n):
        mirror = 2*center - i
        if i < right:
            P[i] = min(right - i, P[mirror])
        while i - 1 - P[i] >= 0 and i + 1 + P[i] < n and t[i + 1 + P[i]] == t[i - 1 - P[i]]:
            P[i] += 1
        if i + P[i] > right:
            center, right = i, i + P[i]
        if P[i] > max_len:
            max_len, center_index = P[i], i

    start = (center_index - max_len) // 2
    return s[start:start + max_len]

if __name__ == "__main__":
    print(manacher("babad"))  # aba or bab
    print(manacher("cbbd"))   # bb

Java Code

public class Manacher {
    public static String longestPalindrome(String s) {
        StringBuilder t = new StringBuilder("#");
        for (char c : s.toCharArray()) t.append(c).append("#");
        int n = t.length();
        int[] P = new int[n];
        int C = 0, R = 0, maxLen = 0, centerIndex = 0;

        for (int i = 0; i < n; i++) {
            int mirror = 2*C - i;
            if (i < R) P[i] = Math.min(R - i, P[mirror]);
            while (i - 1 - P[i] >= 0 && i + 1 + P[i] < n && t.charAt(i + 1 + P[i]) == t.charAt(i - 1 - P[i]))
                P[i]++;
            if (i + P[i] > R) { C = i; R = i + P[i]; }
            if (P[i] > maxLen) { maxLen = P[i]; centerIndex = i; }
        }
        int start = (centerIndex - maxLen) / 2;
        return s.substring(start, start + maxLen);
    }

    public static void main(String[] args) {
        System.out.println(longestPalindrome("babad"));
        System.out.println(longestPalindrome("cbbd"));
    }
}

Time Complexity: O(N)
Space Complexity: O(N)


Huffman Encoding Algorithm

Core Idea

Used for lossless data compression.
Build a binary tree where frequent characters have shorter codes.

  • Count frequencies.

  • Build min-heap of nodes.

  • Merge smallest two nodes until one remains (root).

  • Traverse to assign binary codes.


Python Code

import heapq

class Node:
    def __init__(self, ch, freq):
        self.ch = ch
        self.freq = freq
        self.left = self.right = None
    def __lt__(self, other):
        return self.freq < other.freq

def build_huffman(text):
    freq = {}
    for c in text:
        freq[c] = freq.get(c, 0) + 1
    heap = [Node(ch, fr) for ch, fr in freq.items()]
    heapq.heapify(heap)
    while len(heap) > 1:
        l = heapq.heappop(heap)
        r = heapq.heappop(heap)
        merged = Node(None, l.freq + r.freq)
        merged.left, merged.right = l, r
        heapq.heappush(heap, merged)
    return heap[0]

def generate_codes(node, code="", mapping={}):
    if node:
        if node.ch is not None:
            mapping[node.ch] = code
        generate_codes(node.left, code + "0", mapping)
        generate_codes(node.right, code + "1", mapping)
    return mapping

if __name__ == "__main__":
    text = "hello world"
    root = build_huffman(text)
    codes = generate_codes(root)
    print("Huffman Codes:", codes)
    encoded = "".join(codes[c] for c in text)
    print("Encoded:", encoded)
    print("Decoded:", text)  # demonstration

Java Code

import java.util.*;

class Huffman {
    static class Node implements Comparable<Node> {
        char ch; int freq;
        Node left, right;
        Node(char c, int f) { ch=c; freq=f; }
        public int compareTo(Node o){ return freq - o.freq; }
    }

    static Node buildTree(String text) {
        Map<Character,Integer> freq = new HashMap<>();
        for(char c:text.toCharArray()) freq.put(c, freq.getOrDefault(c,0)+1);
        PriorityQueue<Node> pq = new PriorityQueue<>();
        for(var e:freq.entrySet()) pq.add(new Node(e.getKey(), e.getValue()));
        while(pq.size()>1){
            Node l=pq.poll(), r=pq.poll();
            Node p=new Node('\0', l.freq+r.freq);
            p.left=l; p.right=r; pq.add(p);
        }
        return pq.poll();
    }

    static void genCodes(Node root, String code, Map<Character,String> map){
        if(root==null) return;
        if(root.ch!='\0') map.put(root.ch, code);
        genCodes(root.left, code+"0", map);
        genCodes(root.right, code+"1", map);
    }

    public static void main(String[] args){
        String text="hello world";
        Node root=buildTree(text);
        Map<Character,String> map=new HashMap<>();
        genCodes(root,"",map);
        System.out.println("Codes: "+map);
        StringBuilder enc=new StringBuilder();
        for(char c:text.toCharArray()) enc.append(map.get(c));
        System.out.println("Encoded: "+enc);
        System.out.println("Decoded: "+text);
    }
}

Time Complexity: O(N log N)
Space Complexity: O(N)


Trie Operations (Insert/Search)

Core Idea

Trie (Prefix Tree) stores words character by character.
Efficient for prefix queries and dictionary lookups.
Each node represents a prefix, and a boolean marks the end of a word.


Python Code

class TrieNode:
    def __init__(self):
        self.children = {}
        self.end = False

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        node = self.root
        for c in word:
            node = node.children.setdefault(c, TrieNode())
        node.end = True

    def search(self, word):
        node = self.root
        for c in word:
            if c not in node.children:
                return False
            node = node.children[c]
        return node.end

if __name__ == "__main__":
    trie = Trie()
    for word in ["apple", "app", "bat"]:
        trie.insert(word)
    print(trie.search("app"))
    print(trie.search("batman"))

Java Code

import java.util.*;

class Trie {
    static class Node {
        Map<Character, Node> children = new HashMap<>();
        boolean end;
    }

    private Node root = new Node();

    public void insert(String word) {
        Node node = root;
        for (char c : word.toCharArray())
            node = node.children.computeIfAbsent(c, k -> new Node());
        node.end = true;
    }

    public boolean search(String word) {
        Node node = root;
        for (char c : word.toCharArray()) {
            if (!node.children.containsKey(c)) return false;
            node = node.children.get(c);
        }
        return node.end;
    }

    public static void main(String[] args) {
        Trie trie = new Trie();
        for (String s : List.of("apple", "app", "bat")) trie.insert(s);
        System.out.println(trie.search("app"));
        System.out.println(trie.search("batman"));
    }
}

Time Complexity:

  • Insert: O(L)

  • Search: O(L)
    (L = length of word)
    Space Complexity: O(ALPHABET × total chars)


No comments yet. Be the first to comment.