๐Ÿณ IntuitiveFE
Login
โ† All concepts

Strings & Arrays: Core Patterns

โฑ๏ธ ~4-minute bite ยท solve the sandbox to master

0%lesson
๐Ÿง’

5-Year-Old Metaphor

โ€” The physical, real-world picture. No jargon.

๐Ÿงฉ Strings are just arrays of characters โ€” and arrays are just indexed buckets. Almost every interview problem is really one of five patterns in disguise.

Pattern 1 โ€” Frequency map (the anagram family)

Count how many times each character (or value) appears. Compare maps. Works for: anagram detection, find duplicates, missing number, majority element.

Mental model: Two bags of letter tiles. Anagram = same tiles, different arrangement. Dump both bags, sort the piles, compare. O(n) not O(n log n) โ€” use a hash map instead of sorting.

Pattern 2 โ€” Two pointers (the palindrome/sorted family)

One pointer from each end, or both from the start at different speeds. Works for: palindrome check, two-sum on sorted array, container with most water, remove duplicates.

Mental model: Two fingers reading a book โ€” one from front, one from back. If the characters they point to match, move both inward. If they don't, you found the mismatch.

Pattern 3 โ€” Prefix sums (the range-query family)

Precompute cumulative sums so any range sum is O(1). Works for: subarray sum equals k, number of subarrays with sum โ‰ค target, range sum queries.

arr = [3, 1, 4, 1, 5]
prefix = [0, 3, 4, 8, 9, 14]
sum(i..j) = prefix[j+1] - prefix[i] โ† O(1)

Pattern 4 โ€” Sorting + scan

Sort first (O(n log n)), then a single linear pass finds duplicates, meeting points, or groups. Anagram grouping: sort each word as a key.

Pattern 5 โ€” In-place modification

Use the array itself as a hash map (index = value trick) for O(1) space. Flip the sign of arr[abs(val)-1] to mark "seen". Works for "find missing", "find duplicate".

๐ŸŽ›๏ธ

Interactive Sandbox

โ€” Move something, see it react instantly.
How to use: Type two strings. The frequency map updates live โ€” green bars mean matching counts, red bars mean mismatch.

6 letters

6 letters

โœ“ ANAGRAM

Character frequency map

e
1
=
1
i
1
=
1
l
1
=
1
n
1
=
1
s
1
=
1
t
1
=
1
โ–  String A (left)โ–  String B (right)โ–  matchโ–  mismatch
๐ŸŽฏ

Challenge

Confirm "listen" / "silent" are anagrams, then try "triangle" / "integral".

Solved

โœ… Anagram confirmed! Every character appears the same number of times. Frequency map approach: O(n) time, O(1) space (26-letter alphabet). ๐ŸŽ‰

๐ŸŽฏ

Why Should I Care?

โ€” The exact interview question + the bug it kills.

Exact interview question โ€” is anagram

"Given two strings s and t, return true if t is an anagram of s."

ts
1function isAnagram(s: string, t: string): boolean {
2 if (s.length !== t.length) return false;
3 const freq = new Array(26).fill(0);
4 for (let i = 0; i < s.length; i++) {
5 freq[s.charCodeAt(i) - 97]++;
6 freq[t.charCodeAt(i) - 97]--;
7 }
8 return freq.every((n) => n === 0);
9}
10// One array, two passes merged. O(n) time, O(1) space.

Classic follow-up โ€” valid palindrome

"Given a string, return true if it reads the same forwards and backwards (ignoring non-alphanumeric chars)."

ts
1function isPalindrome(s: string): boolean {
2 let l = 0, r = s.length - 1;
3 while (l < r) {
4 while (l < r && !isAlNum(s[l])) l++;
5 while (l < r && !isAlNum(s[r])) r--;
6 if (s[l].toLowerCase() !== s[r].toLowerCase()) return false;
7 l++; r--;
8 }
9 return true;
10}
11function isAlNum(c: string) { return /[a-zA-Z0-9]/.test(c); }
12// Two pointers from both ends. O(n) time, O(1) space.

Senior variant โ€” subarray sum equals k

ts
1function subarraySum(nums: number[], k: number): number {
2 const prefixCounts = new Map([[0, 1]]); // sum โ†’ count
3 let sum = 0, result = 0;
4 for (const n of nums) {
5 sum += n;
6 result += prefixCounts.get(sum - k) ?? 0; // how many prefixes end here?
7 prefixCounts.set(sum, (prefixCounts.get(sum) ?? 0) + 1);
8 }
9 return result;
10}
11// Prefix sum + hashmap: O(n) time, O(n) space.
12// Key insight: sum(i..j) = prefix[j] - prefix[i-1] = k
13// โ†’ look for prefix[j] - k in the map.

Complexity: choosing the right approach

Problem shapeReach forTime
Count chars / detect anagramFrequency map (array/map)O(n)
Palindrome / two-sum sortedTwo pointersO(n)
Range sum, subarray sum = kPrefix sums + hashmapO(n)
Group by rearrangementSort each word as keyO(n log n)
Find missing / find duplicateIn-place sign flipO(n) O(1)
๐Ÿ”ฌ

The Deep Dive

โ€” Spec refs, engine internals, the minutiae.

Prefix sums โ€” the most underused pattern

Build a cumulative sum array in O(n), then answer any range-sum query in O(1). The key identity: sum(i, j) = prefix[j+1] - prefix[i]. Store the prefix map as you go โ€” you don't always need the full array.

ts
1// Range sum query โ€” O(1) after O(n) build
2class NumArray {
3 private prefix: number[];
4 constructor(nums: number[]) {
5 this.prefix = [0];
6 for (const n of nums) this.prefix.push(this.prefix.at(-1)! + n);
7 }
8 sumRange(left: number, right: number): number {
9 return this.prefix[right + 1] - this.prefix[left];
10 }
11}
12ย 
13// Subarray sum = k: combine prefix map + hashmap
14function subarraySum(nums: number[], k: number): number {
15 const map = new Map([[0, 1]]);
16 let sum = 0, ans = 0;
17 for (const n of nums) {
18 sum += n;
19 ans += map.get(sum - k) ?? 0;
20 map.set(sum, (map.get(sum) ?? 0) + 1);
21 }
22 return ans;
23}

In-place marking โ€” O(1) space for "find missing/duplicate"

When values are bounded by the array size (common in n-length arrays with values 1โ€“n), you can use the array itself as a hash set. Negate arr[abs(val) - 1] to mark "seen". A positive entry at index i means value i+1 was never seen.

ts
1// Find all numbers disappeared from [1, n] โ€” O(n) O(1)
2function findDisappearedNumbers(nums: number[]): number[] {
3 for (const n of nums) {
4 const idx = Math.abs(n) - 1;
5 if (nums[idx] > 0) nums[idx] *= -1; // mark as seen
6 }
7 return nums
8 .map((v, i) => (v > 0 ? i + 1 : 0))
9 .filter(Boolean); // positive โ†’ that value was never seen
10}

โš ๏ธ Mutates the input array. Restore signs afterward if the caller needs it unchanged.

String-specific pitfalls interviewers probe

  • Unicode vs ASCII: charCodeAt(i) - 97 only works for lowercase ASCII. For Unicode, use a Map.
  • String immutability in JS: you can't mutate a string in place โ€” convert to array first, then join.const arr = s.split(""); /* mutate */ return arr.join("");
  • Comparison edge cases: empty string is a palindrome. Two empty strings are anagrams of each other. Clarify before coding.
  • Sliding window vs prefix sum: if the array has negatives and you need "sum = k" (not โ‰ค k), sliding window breaks โ€” use prefix sums + hashmap instead.

Group anagrams โ€” a common medium problem

ts
1function groupAnagrams(strs: string[]): string[][] {
2 const map = new Map<string, string[]>();
3 for (const s of strs) {
4 const key = s.split("").sort().join(""); // sorted chars = canonical key
5 if (!map.has(key)) map.set(key, []);
6 map.get(key)!.push(s);
7 }
8 return [...map.values()];
9}
10// "eat","tea","tan","ate","nat","bat"
11// โ†’ [["eat","tea","ate"],["tan","nat"],["bat"]]
12// Time: O(n ยท k log k) where k = max word length Space: O(nยทk)

The insight: two strings are anagrams iff their sorted characters are equal. Use that as a hashmap key to bucket them in one pass.

Related patterns to know next

  • Sliding Window โ€” combines frequency map with a moving window. "Minimum window substring" = sliding + freq map.
  • Two Pointers โ€” sorting first + two pointers solves 3Sum, 4Sum, container with most water.
  • Binary Search on arrays โ€” when the array is sorted, binary search turns O(n) search into O(log n).
  • Kadane's Algorithm โ€” maximum subarray sum (dynamic programming, not prefix sum โ€” handles negatives differently).
๐ŸŽค

Interview Questions

โ€” Real questions from real interviews โ€” with answers.

Build one frequency map (increment for s, decrement for t), then check all entries are zero.

Precompute cumulative sums: sum(i..j) = prefix[j+1] - prefix[i]. One O(n) build, then O(1) per query.

Sliding window relies on monotonicity โ€” shrinking the window must decrease the sum. Negatives break that invariant.

Skip non-alphanumeric from both ends; compare lowercased characters; meet in the middle.

Use the sign of arr[abs(val)-1] as a boolean flag โ€” negate to mark 'seen', positive means never seen.

๐ŸŽฎ

Memory Game

โ€” Quick quiz โ€” lock the concept in long-term memory.
1/4

What is the time complexity of Kadane's algorithm for maximum subarray sum?