Strings
String algorithms: search (KMP, Boyer-Moore), edit distance, suffix arrays, tries, and the common interview problems built on them.
DSA — strings essentials
EXAMPLE
// ===== Search (substring) =====
// Built-in (most languages):
'hello world'.indexOf('world') // 6
'hello world'.includes('world') // true
// Naive O(n*m), KMP O(n + m), Boyer-Moore sublinear on average.
// Use built-in unless writing the algorithm IS the assignment.
// ===== Anagram check =====
function isAnagram(a, b) {
if (a.length !== b.length) return false;
const c = {};
for (const ch of a) c[ch] = (c[ch] ?? 0) + 1;
for (const ch of b) {
if (!c[ch]) return false;
c[ch]--;
}
return true;
}
// ===== Palindrome (in place) =====
function isPal(s) {
let i = 0, j = s.length - 1;
while (i < j) { if (s[i] !== s[j]) return false; i++; j--; }
return true;
}
// ===== Longest substring without repeating chars =====
function longestUnique(s) {
const last = new Map();
let start = 0, best = 0;
for (let i = 0; i < s.length; i++) {
if (last.has(s[i]) && last.get(s[i]) >= start) start = last.get(s[i]) + 1;
last.set(s[i], i);
best = Math.max(best, i - start + 1);
}
return best;
}
// ===== Edit distance (Levenshtein) =====
function editDistance(a, b) {
const dp = Array.from({ length: a.length + 1 }, () => Array(b.length + 1).fill(0));
for (let i = 0; i <= a.length; i++) dp[i][0] = i;
for (let j = 0; j <= b.length; j++) dp[0][j] = j;
for (let i = 1; i <= a.length; i++) {
for (let j = 1; j <= b.length; j++) {
dp[i][j] = a[i-1] === b[j-1]
? dp[i-1][j-1]
: 1 + Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);
}
}
return dp[a.length][b.length];
}
// ===== Trie (prefix tree) =====
class Trie {
constructor() { this.root = {}; }
insert(word) {
let node = this.root;
for (const ch of word) node = (node[ch] ??= {});
node['*'] = true;
}
startsWith(prefix) {
let node = this.root;
for (const ch of prefix) {
if (!node[ch]) return false;
node = node[ch];
}
return true;
}
}
// Use for: autocomplete, prefix lookup, dictionary search.
// ===== Rolling hash (Rabin-Karp) =====
// Fast substring search across many patterns; great for plagiarism detection.
// ===== KMP failure function =====
// Pattern-internal table speeds up search by avoiding rechecks.
// Useful when you do many searches of the same pattern.
// ===== Common interview patterns =====
// - Two pointers: palindromes, longest substring problems
// - Sliding window: substring with constraints
// - Hash maps: anagrams, character counts
// - Tries: autocomplete, dictionary problems
// - Dynamic programming: edit distance, longest common subseq, regex matching
// ===== Unicode caveats =====
// JS '😀'.length === 2 because of UTF-16 surrogate pairs.
// Use [...str] or Array.from(str) to iterate by code points.
// ===== Patterns to internalise =====
// - indexOf / includes / .startsWith / .endsWith before reaching for KMP
// - Tries for prefix-heavy workloads
// - Hash maps for character counts (anagrams, char frequencies)
// - Sliding window + two pointers cover most substring problems
// ===== Pitfalls =====
// - .length on emoji / accented strings (UTF-16 surrogate pairs)
// - Regex on huge strings without anchors -> ReDoS risk
// - Building strings in a loop with + (O(n^2)); use arrays + .join()
// - Locale-dependent comparisons; use Intl.Collator for proper sorting
Why it matters
String algorithms repay practice. Sliding window + two pointers + hash maps cover most everyday problems; tries shine on prefix searches; edit distance ports to spell check, diff, DNA. The Unicode gotcha (emoji length) bites every couple of years — learn it once.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Strings are arrays of code units.
// Reverse with two pointers:
function reverse(s) {
const a = s.split('');
for (let l = 0, r = a.length - 1; l < r; l++, r--) [a[l], a[r]] = [a[r], a[l]];
return a.join('');
}
Try it Yourself »
Discussion
Loading…