String Algorithms and Patterns 
Overview 
String manipulation is one of the most fundamental and frequently tested topics in coding interviews. This guide covers essential string algorithms, common patterns, and optimization techniques that every software engineer should master.
Core String Operations 
Basic Operations 
- Length: 
s.length- O(1) - Access: 
s[i]ors.charAt(i)- O(1) - Substring: 
s.substring(start, end)- O(n) - Concatenation: 
s1 + s2- O(n + m) - Comparison: 
s1 === s2- O(min(n, m)) 
String Building 
typescript
// Inefficient - creates new string each time
let result = "";
for (let i = 0; i < n; i++) {
    result += chars[i]; // O(n²) total
}
// Efficient - use array and join
const parts: string[] = [];
for (let i = 0; i < n; i++) {
    parts.push(chars[i]);
}
const result = parts.join(""); // O(n) totalCommon String Patterns 
1. Character Frequency Analysis 
Use Case: Anagrams, character counting, permutations
typescript
function getCharFrequency(s: string): Map<string, number> {
    const freq = new Map<string, number>();
    
    for (const char of s) {
        freq.set(char, (freq.get(char) || 0) + 1);
    }
    
    return freq;
}
function areAnagrams(s1: string, s2: string): boolean {
    if (s1.length !== s2.length) return false;
    
    const freq1 = getCharFrequency(s1);
    const freq2 = getCharFrequency(s2);
    
    if (freq1.size !== freq2.size) return false;
    
    for (const [char, count] of freq1) {
        if (freq2.get(char) !== count) return false;
    }
    
    return true;
}2. Sliding Window for Substrings 
Use Case: Longest substring problems, pattern matching
typescript
function lengthOfLongestSubstring(s: string): number {
    const seen = new Set<string>();
    let left = 0;
    let maxLength = 0;
    
    for (let right = 0; right < s.length; right++) {
        while (seen.has(s[right])) {
            seen.delete(s[left]);
            left++;
        }
        
        seen.add(s[right]);
        maxLength = Math.max(maxLength, right - left + 1);
    }
    
    return maxLength;
}
function minWindow(s: string, t: string): string {
    const targetFreq = getCharFrequency(t);
    const windowFreq = new Map<string, number>();
    
    let left = 0;
    let minLen = Infinity;
    let minStart = 0;
    let formed = 0;
    const required = targetFreq.size;
    
    for (let right = 0; right < s.length; right++) {
        const char = s[right];
        windowFreq.set(char, (windowFreq.get(char) || 0) + 1);
        
        if (targetFreq.has(char) && windowFreq.get(char) === targetFreq.get(char)) {
            formed++;
        }
        
        while (left <= right && formed === required) {
            if (right - left + 1 < minLen) {
                minLen = right - left + 1;
                minStart = left;
            }
            
            const leftChar = s[left];
            windowFreq.set(leftChar, windowFreq.get(leftChar)! - 1);
            
            if (targetFreq.has(leftChar) && windowFreq.get(leftChar)! < targetFreq.get(leftChar)!) {
                formed--;
            }
            
            left++;
        }
    }
    
    return minLen === Infinity ? "" : s.substring(minStart, minStart + minLen);
}3. Palindrome Patterns 
Use Case: Palindrome detection, longest palindromic substring
typescript
function isPalindrome(s: string): boolean {
    let left = 0;
    let right = s.length - 1;
    
    while (left < right) {
        if (s[left] !== s[right]) {
            return false;
        }
        left++;
        right--;
    }
    
    return true;
}
function longestPalindrome(s: string): string {
    if (!s || s.length < 2) return s;
    
    let start = 0;
    let maxLen = 1;
    
    function expandAroundCenter(left: number, right: number): void {
        while (left >= 0 && right < s.length && s[left] === s[right]) {
            const currentLen = right - left + 1;
            if (currentLen > maxLen) {
                start = left;
                maxLen = currentLen;
            }
            left--;
            right++;
        }
    }
    
    for (let i = 0; i < s.length; i++) {
        // Odd length palindromes
        expandAroundCenter(i, i);
        // Even length palindromes
        expandAroundCenter(i, i + 1);
    }
    
    return s.substring(start, start + maxLen);
}4. String Matching Algorithms 
KMP (Knuth-Morris-Pratt) Algorithm 
typescript
function buildLPS(pattern: string): number[] {
    const lps = new Array(pattern.length).fill(0);
    let len = 0;
    let i = 1;
    
    while (i < pattern.length) {
        if (pattern[i] === pattern[len]) {
            len++;
            lps[i] = len;
            i++;
        } else {
            if (len !== 0) {
                len = lps[len - 1];
            } else {
                lps[i] = 0;
                i++;
            }
        }
    }
    
    return lps;
}
function KMPSearch(text: string, pattern: string): number[] {
    const lps = buildLPS(pattern);
    const result: number[] = [];
    
    let i = 0; // text index
    let j = 0; // pattern index
    
    while (i < text.length) {
        if (pattern[j] === text[i]) {
            i++;
            j++;
        }
        
        if (j === pattern.length) {
            result.push(i - j);
            j = lps[j - 1];
        } else if (i < text.length && pattern[j] !== text[i]) {
            if (j !== 0) {
                j = lps[j - 1];
            } else {
                i++;
            }
        }
    }
    
    return result;
}Rabin-Karp Algorithm (Rolling Hash) 
typescript
function rabinKarp(text: string, pattern: string): number[] {
    const base = 256;
    const prime = 101;
    const result: number[] = [];
    
    const patternLen = pattern.length;
    const textLen = text.length;
    
    let patternHash = 0;
    let textHash = 0;
    let h = 1;
    
    // Calculate h = base^(patternLen-1) % prime
    for (let i = 0; i < patternLen - 1; i++) {
        h = (h * base) % prime;
    }
    
    // Calculate hash for pattern and first window
    for (let i = 0; i < patternLen; i++) {
        patternHash = (base * patternHash + pattern.charCodeAt(i)) % prime;
        textHash = (base * textHash + text.charCodeAt(i)) % prime;
    }
    
    // Slide the pattern over text
    for (let i = 0; i <= textLen - patternLen; i++) {
        if (patternHash === textHash) {
            // Check character by character
            let match = true;
            for (let j = 0; j < patternLen; j++) {
                if (text[i + j] !== pattern[j]) {
                    match = false;
                    break;
                }
            }
            if (match) result.push(i);
        }
        
        // Calculate hash for next window
        if (i < textLen - patternLen) {
            textHash = (base * (textHash - text.charCodeAt(i) * h) + text.charCodeAt(i + patternLen)) % prime;
            if (textHash < 0) textHash += prime;
        }
    }
    
    return result;
}5. String Transformation 
Use Case: Edit distance, string conversion
typescript
function editDistance(s1: string, s2: string): number {
    const m = s1.length;
    const n = s2.length;
    const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
    
    // Initialize base cases
    for (let i = 0; i <= m; i++) dp[i][0] = i;
    for (let j = 0; j <= n; j++) dp[0][j] = j;
    
    for (let i = 1; i <= m; i++) {
        for (let j = 1; j <= n; j++) {
            if (s1[i - 1] === s2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = 1 + Math.min(
                    dp[i - 1][j],     // deletion
                    dp[i][j - 1],     // insertion
                    dp[i - 1][j - 1]  // substitution
                );
            }
        }
    }
    
    return dp[m][n];
}Advanced String Techniques 
1. Trie (Prefix Tree) 
typescript
class TrieNode {
    children: Map<string, TrieNode> = new Map();
    isEndOfWord: boolean = false;
}
class Trie {
    private root: TrieNode;
    
    constructor() {
        this.root = new TrieNode();
    }
    
    insert(word: string): void {
        let current = this.root;
        
        for (const char of word) {
            if (!current.children.has(char)) {
                current.children.set(char, new TrieNode());
            }
            current = current.children.get(char)!;
        }
        
        current.isEndOfWord = true;
    }
    
    search(word: string): boolean {
        let current = this.root;
        
        for (const char of word) {
            if (!current.children.has(char)) {
                return false;
            }
            current = current.children.get(char)!;
        }
        
        return current.isEndOfWord;
    }
    
    startsWith(prefix: string): boolean {
        let current = this.root;
        
        for (const char of prefix) {
            if (!current.children.has(char)) {
                return false;
            }
            current = current.children.get(char)!;
        }
        
        return true;
    }
}2. Suffix Array and LCP 
typescript
function buildSuffixArray(s: string): number[] {
    const n = s.length;
    const suffixes: Array<{index: number, suffix: string}> = [];
    
    for (let i = 0; i < n; i++) {
        suffixes.push({
            index: i,
            suffix: s.substring(i)
        });
    }
    
    suffixes.sort((a, b) => a.suffix.localeCompare(b.suffix));
    
    return suffixes.map(item => item.index);
}
function longestCommonPrefix(s1: string, s2: string): number {
    let i = 0;
    while (i < s1.length && i < s2.length && s1[i] === s2[i]) {
        i++;
    }
    return i;
}String Validation Patterns 
1. Parentheses Validation 
typescript
function isValidParentheses(s: string): boolean {
    const stack: string[] = [];
    const pairs: Record<string, string> = {
        ')': '(',
        '}': '{',
        ']': '['
    };
    
    for (const char of s) {
        if (char in pairs) {
            if (stack.length === 0 || stack.pop() !== pairs[char]) {
                return false;
            }
        } else {
            stack.push(char);
        }
    }
    
    return stack.length === 0;
}2. Regular Expression Matching 
typescript
function isMatch(s: string, p: string): boolean {
    const m = s.length;
    const n = p.length;
    const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(false));
    
    dp[0][0] = true;
    
    // Handle patterns like a*, a*b*, a*b*c*
    for (let j = 2; j <= n; j += 2) {
        if (p[j - 1] === '*') {
            dp[0][j] = dp[0][j - 2];
        }
    }
    
    for (let i = 1; i <= m; i++) {
        for (let j = 1; j <= n; j++) {
            if (p[j - 1] === '*') {
                dp[i][j] = dp[i][j - 2]; // Zero occurrences
                if (p[j - 2] === '.' || p[j - 2] === s[i - 1]) {
                    dp[i][j] = dp[i][j] || dp[i - 1][j]; // One or more occurrences
                }
            } else if (p[j - 1] === '.' || p[j - 1] === s[i - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            }
        }
    }
    
    return dp[m][n];
}Interview Tips 
Common String Interview Patterns 
- Character Frequency: Use HashMap/Map for counting
 - Sliding Window: For substring problems
 - Two Pointers: For palindromes and comparisons
 - Dynamic Programming: For edit distance and pattern matching
 - Stack: For parentheses and expression validation
 
Optimization Strategies 
- Avoid String Concatenation: Use arrays and join
 - Use Character Codes: 
charCodeAt()for faster comparisons - Early Termination: Break loops when possible
 - Space-Time Tradeoffs: HashMap vs nested loops
 
Edge Cases to Consider 
- Empty strings
 - Single character strings
 - All same characters
 - Unicode and special characters
 - Case sensitivity
 - Null/undefined inputs
 
Practice Problems 
Easy 
- Valid Anagram
 - Valid Palindrome
 - Implement strStr()
 - Longest Common Prefix
 - Reverse String
 
Medium 
- Longest Substring Without Repeating Characters
 - Longest Palindromic Substring
 - Group Anagrams
 - String to Integer (atoi)
 - Minimum Window Substring
 - Valid Parentheses
 
Hard 
- Edit Distance
 - Regular Expression Matching
 - Wildcard Matching
 - Shortest Palindrome
 - Text Justification
 
Time and Space Complexity 
| Algorithm | Time | Space | Use Case | 
|---|---|---|---|
| Character Frequency | O(n) | O(k) | Anagrams, counting | 
| Sliding Window | O(n) | O(k) | Substring problems | 
| KMP Search | O(n + m) | O(m) | Pattern matching | 
| Rabin-Karp | O(n + m) | O(1) | Multiple pattern search | 
| Edit Distance | O(n × m) | O(n × m) | String transformation | 
| Trie Operations | O(m) | O(ALPHABET_SIZE × N × M) | Prefix matching | 
Where:
- n = length of text
 - m = length of pattern
 - k = size of character set
 - N = number of words
 - M = average length of words
 
Problem Implementations 
This directory contains the following problem solutions:
TypeScript Solutions 
- Bear and Steady Gene - Find minimum substring to make gene steady
 - Sherlock and Anagrams - Count anagrammatic pairs in string
 - Sherlock and the Valid String - Determine if string can be made valid
 
These implementations demonstrate practical applications of the string algorithms and patterns covered in this guide.