Olox Olox

Theme

Documentation
Back to Home

String Manipulation Patterns

10 min read

String Manipulation Patterns

📚 Overview

String problems appear frequently in interviews. Master these patterns for substring, palindrome, and pattern matching problems.


1️⃣ Two Pointer String Patterns

Valid Palindrome (LC 125)

def is_palindrome(s: str) -> bool:
    """Check if string is palindrome (alphanumeric only)"""
    left, right = 0, len(s) - 1
    
    while left < right:
        while left < right and not s[left].isalnum():
            left += 1
        while left < right and not s[right].isalnum():
            right -= 1
        
        if s[left].lower() != s[right].lower():
            return False
        
        left += 1
        right -= 1
    
    return True

Valid Palindrome II (LC 680)

def valid_palindrome(s: str) -> bool:
    """Can be palindrome by removing at most one character"""
    def is_palindrome_range(left: int, right: int) -> bool:
        while left < right:
            if s[left] != s[right]:
                return False
            left += 1
            right -= 1
        return True
    
    left, right = 0, len(s) - 1
    
    while left < right:
        if s[left] != s[right]:
            # Try removing either character
            return is_palindrome_range(left + 1, right) or \
                   is_palindrome_range(left, right - 1)
        left += 1
        right -= 1
    
    return True

2️⃣ Expand Around Center

Longest Palindromic Substring (LC 5)

def longest_palindrome(s: str) -> str:
    """Find longest palindromic substring"""
    if not s:
        return ""
    
    def expand(left: int, right: int) -> str:
        while left >= 0 and right < len(s) and s[left] == s[right]:
            left -= 1
            right += 1
        return s[left + 1:right]
    
    result = ""
    for i in range(len(s)):
        # Odd length palindrome
        odd = expand(i, i)
        if len(odd) > len(result):
            result = odd
        
        # Even length palindrome
        even = expand(i, i + 1)
        if len(even) > len(result):
            result = even
    
    return result

Palindromic Substrings Count (LC 647)

def count_substrings(s: str) -> int:
    """Count all palindromic substrings"""
    count = 0
    
    def expand(left: int, right: int) -> int:
        cnt = 0
        while left >= 0 and right < len(s) and s[left] == s[right]:
            cnt += 1
            left -= 1
            right += 1
        return cnt
    
    for i in range(len(s)):
        count += expand(i, i)      # Odd
        count += expand(i, i + 1)  # Even
    
    return count

3️⃣ Sliding Window for Strings

Longest Substring Without Repeating Characters (LC 3)

def length_of_longest_substring(s: str) -> int:
    """Longest substring with unique characters"""
    char_index = {}
    max_len = 0
    left = 0
    
    for right, char in enumerate(s):
        if char in char_index and char_index[char] >= left:
            left = char_index[char] + 1
        
        char_index[char] = right
        max_len = max(max_len, right - left + 1)
    
    return max_len

Minimum Window Substring (LC 76)

def min_window(s: str, t: str) -> str:
    """Minimum window containing all characters of t"""
    from collections import Counter
    
    if not t or not s:
        return ""
    
    need = Counter(t)
    have = {}
    formed = 0
    required = len(need)
    
    result = ""
    min_len = float('inf')
    left = 0
    
    for right, char in enumerate(s):
        have[char] = have.get(char, 0) + 1
        
        if char in need and have[char] == need[char]:
            formed += 1
        
        while formed == required:
            if right - left + 1 < min_len:
                min_len = right - left + 1
                result = s[left:right + 1]
            
            left_char = s[left]
            have[left_char] -= 1
            if left_char in need and have[left_char] < need[left_char]:
                formed -= 1
            left += 1
    
    return result

Find All Anagrams (LC 438)

def find_anagrams(s: str, p: str) -> list[int]:
    """Find all start indices of anagrams of p in s"""
    from collections import Counter
    
    if len(p) > len(s):
        return []
    
    p_count = Counter(p)
    s_count = Counter(s[:len(p)])
    
    result = []
    if s_count == p_count:
        result.append(0)
    
    for i in range(len(p), len(s)):
        # Add new char
        s_count[s[i]] += 1
        
        # Remove old char
        old = s[i - len(p)]
        s_count[old] -= 1
        if s_count[old] == 0:
            del s_count[old]
        
        if s_count == p_count:
            result.append(i - len(p) + 1)
    
    return result

4️⃣ String Matching

KMP Algorithm

def kmp_search(text: str, pattern: str) -> int:
    """KMP pattern matching - O(n + m)"""
    if not pattern:
        return 0
    
    # Build failure function
    def build_lps(p: str) -> list[int]:
        lps = [0] * len(p)
        length = 0
        i = 1
        
        while i < len(p):
            if p[i] == p[length]:
                length += 1
                lps[i] = length
                i += 1
            elif length > 0:
                length = lps[length - 1]
            else:
                lps[i] = 0
                i += 1
        
        return lps
    
    lps = build_lps(pattern)
    i = j = 0
    
    while i < len(text):
        if text[i] == pattern[j]:
            i += 1
            j += 1
            if j == len(pattern):
                return i - j
        elif j > 0:
            j = lps[j - 1]
        else:
            i += 1
    
    return -1

Rabin-Karp (Rolling Hash)

def rabin_karp(text: str, pattern: str) -> int:
    """Rabin-Karp using rolling hash"""
    if len(pattern) > len(text):
        return -1
    
    BASE = 26
    MOD = 10**9 + 7
    
    # Compute hash for pattern and first window
    pattern_hash = 0
    window_hash = 0
    power = 1
    
    for i in range(len(pattern)):
        pattern_hash = (pattern_hash * BASE + ord(pattern[i])) % MOD
        window_hash = (window_hash * BASE + ord(text[i])) % MOD
        if i < len(pattern) - 1:
            power = (power * BASE) % MOD
    
    # Slide window
    for i in range(len(text) - len(pattern) + 1):
        if pattern_hash == window_hash:
            if text[i:i + len(pattern)] == pattern:
                return i
        
        if i < len(text) - len(pattern):
            # Update hash
            window_hash = (window_hash - ord(text[i]) * power) % MOD
            window_hash = (window_hash * BASE + ord(text[i + len(pattern)])) % MOD
    
    return -1

5️⃣ Palindrome Partitioning

Palindrome Partitioning (LC 131)

def partition(s: str) -> list[list[str]]:
    """All ways to partition into palindromes"""
    result = []
    
    def is_palindrome(start: int, end: int) -> bool:
        while start < end:
            if s[start] != s[end]:
                return False
            start += 1
            end -= 1
        return True
    
    def backtrack(start: int, path: list[str]):
        if start == len(s):
            result.append(path[:])
            return
        
        for end in range(start, len(s)):
            if is_palindrome(start, end):
                path.append(s[start:end + 1])
                backtrack(end + 1, path)
                path.pop()
    
    backtrack(0, [])
    return result

Min Cuts for Palindrome Partitioning (LC 132)

def min_cut(s: str) -> int:
    """Minimum cuts for palindrome partitioning"""
    n = len(s)
    
    # Precompute palindrome table
    is_palindrome = [[False] * n for _ in range(n)]
    for i in range(n):
        is_palindrome[i][i] = True
    for i in range(n - 1):
        is_palindrome[i][i + 1] = s[i] == s[i + 1]
    for length in range(3, n + 1):
        for i in range(n - length + 1):
            j = i + length - 1
            is_palindrome[i][j] = s[i] == s[j] and is_palindrome[i + 1][j - 1]
    
    # DP for min cuts
    dp = list(range(n))  # dp[i] = min cuts for s[0:i+1]
    
    for i in range(1, n):
        if is_palindrome[0][i]:
            dp[i] = 0
        else:
            for j in range(i):
                if is_palindrome[j + 1][i]:
                    dp[i] = min(dp[i], dp[j] + 1)
    
    return dp[n - 1]

6️⃣ String Building & Transformation

Longest Common Prefix (LC 14)

def longest_common_prefix(strs: list[str]) -> str:
    """Find longest common prefix"""
    if not strs:
        return ""
    
    prefix = strs[0]
    
    for s in strs[1:]:
        while not s.startswith(prefix):
            prefix = prefix[:-1]
            if not prefix:
                return ""
    
    return prefix

Group Anagrams (LC 49)

def group_anagrams(strs: list[str]) -> list[list[str]]:
    """Group strings that are anagrams"""
    from collections import defaultdict
    
    groups = defaultdict(list)
    
    for s in strs:
        key = tuple(sorted(s))
        groups[key].append(s)
    
    return list(groups.values())

Encode and Decode Strings (LC 271)

class Codec:
    def encode(self, strs: list[str]) -> str:
        """Encode list of strings to single string"""
        return ''.join(f'{len(s)}#{s}' for s in strs)
    
    def decode(self, s: str) -> list[str]:
        """Decode string back to list"""
        result = []
        i = 0
        
        while i < len(s):
            j = s.index('#', i)
            length = int(s[i:j])
            result.append(s[j + 1:j + 1 + length])
            i = j + 1 + length
        
        return result

7️⃣ String to Integer

Atoi Implementation (LC 8)

def my_atoi(s: str) -> int:
    """String to integer with overflow handling"""
    INT_MAX = 2**31 - 1
    INT_MIN = -2**31
    
    i = 0
    n = len(s)
    
    # Skip whitespace
    while i < n and s[i] == ' ':
        i += 1
    
    if i == n:
        return 0
    
    # Handle sign
    sign = 1
    if s[i] == '-':
        sign = -1
        i += 1
    elif s[i] == '+':
        i += 1
    
    # Parse digits
    result = 0
    while i < n and s[i].isdigit():
        digit = int(s[i])
        
        # Check overflow
        if result > (INT_MAX - digit) // 10:
            return INT_MAX if sign == 1 else INT_MIN
        
        result = result * 10 + digit
        i += 1
    
    return sign * result

8️⃣ Parentheses Problems

Valid Parentheses (LC 20)

def is_valid(s: str) -> bool:
    """Check balanced parentheses"""
    stack = []
    mapping = {')': '(', '}': '{', ']': '['}
    
    for char in s:
        if char in mapping:
            if not stack or stack.pop() != mapping[char]:
                return False
        else:
            stack.append(char)
    
    return len(stack) == 0

Generate Parentheses (LC 22)

def generate_parenthesis(n: int) -> list[str]:
    """Generate all valid parentheses combinations"""
    result = []
    
    def backtrack(path: list[str], open_count: int, close_count: int):
        if len(path) == 2 * n:
            result.append(''.join(path))
            return
        
        if open_count < n:
            path.append('(')
            backtrack(path, open_count + 1, close_count)
            path.pop()
        
        if close_count < open_count:
            path.append(')')
            backtrack(path, open_count, close_count + 1)
            path.pop()
    
    backtrack([], 0, 0)
    return result

Longest Valid Parentheses (LC 32)

def longest_valid_parentheses(s: str) -> int:
    """Find length of longest valid parentheses substring"""
    max_len = 0
    stack = [-1]  # Base index
    
    for i, char in enumerate(s):
        if char == '(':
            stack.append(i)
        else:
            stack.pop()
            if not stack:
                stack.append(i)  # New base
            else:
                max_len = max(max_len, i - stack[-1])
    
    return max_len

9️⃣ Word Problems

Word Break (LC 139)

def word_break(s: str, word_dict: list[str]) -> bool:
    """Can s be segmented into dictionary words"""
    word_set = set(word_dict)
    n = len(s)
    dp = [False] * (n + 1)
    dp[0] = True
    
    for i in range(1, n + 1):
        for j in range(i):
            if dp[j] and s[j:i] in word_set:
                dp[i] = True
                break
    
    return dp[n]

Word Ladder (LC 127)

def ladder_length(begin_word: str, end_word: str, word_list: list[str]) -> int:
    """Shortest transformation sequence length"""
    from collections import deque
    
    word_set = set(word_list)
    if end_word not in word_set:
        return 0
    
    queue = deque([(begin_word, 1)])
    visited = {begin_word}
    
    while queue:
        word, length = queue.popleft()
        
        if word == end_word:
            return length
        
        for i in range(len(word)):
            for c in 'abcdefghijklmnopqrstuvwxyz':
                next_word = word[:i] + c + word[i + 1:]
                if next_word in word_set and next_word not in visited:
                    visited.add(next_word)
                    queue.append((next_word, length + 1))
    
    return 0

🔟 Advanced String DP

Edit Distance (LC 72)

def min_distance(word1: str, word2: str) -> int:
    """Minimum operations to transform word1 to word2"""
    m, n = len(word1), len(word2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    for i in range(m + 1):
        dp[i][0] = i
    for j in range(n + 1):
        dp[0][j] = j
    
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if word1[i - 1] == word2[j - 1]:
                dp[i][j] = dp[i - 1][j - 1]
            else:
                dp[i][j] = 1 + min(
                    dp[i - 1][j],      # Delete
                    dp[i][j - 1],      # Insert
                    dp[i - 1][j - 1]   # Replace
                )
    
    return dp[m][n]

Distinct Subsequences (LC 115)

def num_distinct(s: str, t: str) -> int:
    """Count distinct subsequences of s that equal t"""
    m, n = len(s), len(t)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    # Empty t can be formed from any prefix of s
    for i in range(m + 1):
        dp[i][0] = 1
    
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if s[i - 1] == t[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
            else:
                dp[i][j] = dp[i - 1][j]
    
    return dp[m][n]

ProblemLC #Pattern
Longest Palindromic Substring5Expand Around Center
Valid Palindrome II680Two Pointers
Minimum Window Substring76Sliding Window
Find All Anagrams438Sliding Window
Group Anagrams49Hash Map
Palindrome Partitioning131Backtracking
Word Break139DP
Word Ladder127BFS
Edit Distance722D DP
Longest Valid Parentheses32Stack
Generate Parentheses22Backtracking
Longest Common Prefix14Iteration
KMP Pattern Matching-Pattern Matching
Distinct Subsequences1152D DP
Implement strStr28Pattern Matching

Last Updated: 2024