π― JavaScript Hashing β Top 25 Interview Questions
Q1 β Two Sum
π§ How to Approach This
Step 1 β Recognize the pattern: Complement lookup β store seen values in a Map.
Step 2 β Brute force (what NOT to do): Nested loops O(nΒ²).
Step 3 β Optimal insight: As you iterate, check if target - num is already in Map.
Step 4 β Algorithm:
1. For each num: if Map has (target - num) β return [map.get(comp), i]
2. Else map.set(num, i)
Step 5 β Edge cases: Same index cannot be used twice (check before inserting).
function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const comp = target - nums[i];
if (map.has(comp)) return [map.get(comp), i];
map.set(nums[i], i);
}
return [];
}
Time: O(n) | Space: O(n)
Q2 β Group Anagrams
π§ How to Approach This
Step 1 β Recognize the pattern: Anagrams share the same sorted character key.
Step 2 β Brute force (what NOT to do): Compare each pair β O(nΒ² Β· k log k).
Step 3 β Optimal insight: Sort each string as key; group by key in a Map.
Step 4 β Algorithm:
1. For each string: sort chars β key
2. Group strings by key
Step 5 β Edge cases: Single string; empty string.
function groupAnagrams(strs) {
const map = new Map();
for (const s of strs) {
const key = s.split('').sort().join('');
if (!map.has(key)) map.set(key, []);
map.get(key).push(s);
}
return [...map.values()];
}
Time: O(n Β· k log k) | Space: O(n Β· k)
Q3 β Top K Frequent Elements
π§ How to Approach This
Step 1 β Recognize the pattern: Frequency map + bucket sort by frequency.
Step 2 β Brute force (what NOT to do): Sort all by frequency β O(n log n).
Step 3 β Optimal insight: Bucket sort with index = frequency; collect from high to low.
Step 4 β Algorithm:
1. Build frequency Map
2. Bucket by frequency (size n+1)
3. Scan from high bucket; collect k elements
Step 5 β Edge cases: All unique; k = n.
function topKFrequent(nums, k) {
const freq = new Map();
for (const n of nums) freq.set(n, (freq.get(n) ?? 0) + 1);
const buckets = Array.from({ length: nums.length + 1 }, () => []);
for (const [n, f] of freq) buckets[f].push(n);
const result = [];
for (let i = buckets.length - 1; i >= 0 && result.length < k; i--)
for (const n of buckets[i]) { result.push(n); if (result.length === k) break; }
return result;
}
Time: O(n) | Space: O(n)
Q4 β Valid Anagram
π§ How to Approach This
Step 1 β Recognize the pattern: Frequency balance β increment for s, decrement for t.
Step 2 β Brute force (what NOT to do): Sort both strings β O(n log n).
Step 3 β Optimal insight: Single Map; all values must reach zero.
Step 4 β Algorithm:
1. If lengths differ β false
2. freq.set(c, +1) for s; freq.set(c, -1) for t
3. Any non-zero value β false
Step 5 β Edge cases: Unicode characters.
function isAnagram(s, t) {
if (s.length !== t.length) return false;
const freq = new Map();
for (const c of s) freq.set(c, (freq.get(c) ?? 0) + 1);
for (const c of t) {
if (!freq.has(c) || freq.get(c) === 0) return false;
freq.set(c, freq.get(c) - 1);
}
return true;
}
Time: O(n) | Space: O(1) β at most 26 chars
Q5 β Contains Duplicate
π§ How to Approach This
Step 1 β Recognize the pattern: Set membership check as you iterate.
Step 2 β Brute force (what NOT to do): Nested loops O(nΒ²).
Step 3 β Optimal insight: If element already in Set β duplicate found.
Step 4 β Algorithm:
1. For each num: if in Set β return true; add to Set
2. Return false
Step 5 β Edge cases: Empty array β false; single element β false.
function containsDuplicate(nums) {
const seen = new Set();
for (const n of nums) {
if (seen.has(n)) return true;
seen.add(n);
}
return false;
}
Time: O(n) | Space: O(n)
Q6 β LRU Cache
π§ How to Approach This
Step 1 β Recognize the pattern: Map + implicit doubly linked list using Map insertion order.
Step 2 β Brute force (what NOT to do): Array with linear search β O(n) per operation.
Step 3 β Optimal insight: JavaScript Map preserves insertion order; delete + re-insert = move to end (most recent).
Step 4 β Algorithm:
1. get(key): if exists β delete + re-insert (refresh), return val; else -1
2. put(key, val): if exists β delete; set new; if over cap β delete Map.keys().next()
Step 5 β Edge cases: Capacity 1; repeated puts.
class LRUCache {
constructor(capacity) {
this.cap = capacity;
this.map = new Map(); // LRU at front, MRU at back
}
get(key) {
if (!this.map.has(key)) return -1;
const val = this.map.get(key);
this.map.delete(key);
this.map.set(key, val); // move to end (MRU)
return val;
}
put(key, value) {
if (this.map.has(key)) this.map.delete(key);
this.map.set(key, value);
if (this.map.size > this.cap) this.map.delete(this.map.keys().next().value);
}
}
Time: O(1) get/put | Space: O(capacity)
Q7 β Subarray Sum Equals K
π§ How to Approach This
Step 1 β Recognize the pattern: Prefix sum; count(sum - k) tells us subarrays ending here that sum to k.
Step 2 β Brute force (what NOT to do): All subarrays O(nΒ²) or O(nΒ³).
Step 3 β Optimal insight: Map stores prefix sum β count; look up sum-k each step.
Step 4 β Algorithm:
1. map.set(0, 1); sum=0, result=0
2. sum += n; result += map.get(sum-k) ?? 0; map.set(sum, ...)
Step 5 β Edge cases: Negative numbers; k=0.
function subarraySum(nums, k) {
const map = new Map([[0, 1]]);
let sum = 0, count = 0;
for (const n of nums) {
sum += n;
count += map.get(sum - k) ?? 0;
map.set(sum, (map.get(sum) ?? 0) + 1);
}
return count;
}
Time: O(n) | Space: O(n)
Q8 β Longest Consecutive Sequence
π§ How to Approach This
Step 1 β Recognize the pattern: For each sequence start, count streak using Set.
Step 2 β Brute force (what NOT to do): Sort then count β O(n log n).
Step 3 β Optimal insight: Only start counting when no n-1 in Set.
Step 4 β Algorithm:
1. Build Set from nums
2. For each n: if n-1 not in Set β count streak n, n+1, n+2...
3. Track max streak
Step 5 β Edge cases: Empty array; duplicates.
function longestConsecutive(nums) {
const set = new Set(nums); let best = 0;
for (const n of set) {
if (!set.has(n - 1)) {
let curr = n, streak = 1;
while (set.has(curr + 1)) { curr++; streak++; }
best = Math.max(best, streak);
}
}
return best;
}
Time: O(n) | Space: O(n)
Q9 β 4Sum II
π§ How to Approach This
Step 1 β Recognize the pattern: Split into 2 pairs; count complementary sums.
Step 2 β Brute force (what NOT to do): 4 nested loops O(nβ΄).
Step 3 β Optimal insight: Map all sums of first two arrays; scan sums of last two for -(sum).
Step 4 β Algorithm:
1. Map a+b β count for all pairs in nums1, nums2
2. For all c,d: result += map.get(-(c+d)) ?? 0
Step 5 β Edge cases: All zeros.
function fourSumCount(nums1, nums2, nums3, nums4) {
const map = new Map(); let count = 0;
for (const a of nums1) for (const b of nums2) map.set(a+b, (map.get(a+b)??0)+1);
for (const c of nums3) for (const d of nums4) count += map.get(-(c+d)) ?? 0;
return count;
}
Time: O(nΒ²) | Space: O(nΒ²)
Q10 β Intersection of Two Arrays
π§ How to Approach This
Step 1 β Recognize the pattern: Set for first array; check second array elements.
Step 2 β Brute force (what NOT to do): Nested loops β O(nΒ·m).
Step 3 β Optimal insight: Set from nums1; iterate nums2, collect matches in result Set.
Step 4 β Algorithm:
1. set1 = new Set(nums1)
2. For each in nums2: if in set1 β add to result Set
Step 5 β Edge cases: No intersection; duplicates.
function intersection(nums1, nums2) {
const set1 = new Set(nums1), result = new Set();
for (const n of nums2) if (set1.has(n)) result.add(n);
return [...result];
}
Time: O(n + m) | Space: O(n)
Q11 β Happy Number
π§ How to Approach This
Step 1 β Recognize the pattern: Cycle detection using Set.
Step 2 β Brute force (what NOT to do): Run indefinitely without detection.
Step 3 β Optimal insight: Track seen numbers; if 1 β happy; if repeat β not happy.
Step 4 β Algorithm:
1. While n != 1 and n not in seen: compute digit-square sum; add to seen
2. Return n === 1
Step 5 β Edge cases: n=1 immediately true; n=7 is happy.
function isHappy(n) {
const seen = new Set();
while (n !== 1) {
if (seen.has(n)) return false;
seen.add(n);
let sum = 0;
while (n > 0) { const d = n % 10; sum += d * d; n = Math.floor(n / 10); }
n = sum;
}
return true;
}
Time: O(log n) | Space: O(log n)
Q12 β Isomorphic Strings
π§ How to Approach This
Step 1 β Recognize the pattern: Bijection β dual Maps ensuring one-to-one mapping.
Step 2 β Brute force (what NOT to do): Compare all possible mappings.
Step 3 β Optimal insight: Two Maps (sβt and tβs); conflict in either = not isomorphic.
Step 4 β Algorithm:
1. For each pair (cs, ct): check both Maps for conflicts
Step 5 β Edge cases: Repeated same chars; single char.
function isIsomorphic(s, t) {
const st = new Map(), ts = new Map();
for (let i = 0; i < s.length; i++) {
const cs = s[i], ct = t[i];
if (st.has(cs) && st.get(cs) !== ct) return false;
if (ts.has(ct) && ts.get(ct) !== cs) return false;
st.set(cs, ct); ts.set(ct, cs);
}
return true;
}
Time: O(n) | Space: O(1)
Q13 β Word Pattern
π§ How to Approach This
Step 1 β Recognize the pattern: Bijection at word level β same as isomorphic strings.
Step 2 β Brute force (what NOT to do): Generate all pattern mappings.
Step 3 β Optimal insight: Dual Maps: charβword and wordβchar.
Step 4 β Algorithm:
1. Split s; if count differs β false
2. Dual Map check per position
Step 5 β Edge cases: Pattern length β word count.
function wordPattern(pattern, s) {
const words = s.split(' ');
if (pattern.length !== words.length) return false;
const cw = new Map(), wc = new Map();
for (let i = 0; i < pattern.length; i++) {
const c = pattern[i], w = words[i];
if (cw.has(c) && cw.get(c) !== w) return false;
if (wc.has(w) && wc.get(w) !== c) return false;
cw.set(c, w); wc.set(w, c);
}
return true;
}
Time: O(n) | Space: O(n)
Q14 β First Missing Positive
π§ How to Approach This
Step 1 β Recognize the pattern: Cyclic sort β use array indices as hash.
Step 2 β Brute force (what NOT to do): Sort + scan β O(n log n).
Step 3 β Optimal insight: Place each num in [1..n] at index num-1; first mismatch = answer.
Step 4 β Algorithm:
1. Cyclic sort: while nums[i] in range and not at correct pos β swap
2. First index where nums[i] !== i+1 β return i+1
Step 5 β Edge cases: [1,2,3] β 4; all negatives β 1.
function firstMissingPositive(nums) {
const n = nums.length;
for (let i = 0; i < n; i++) {
while (nums[i] >= 1 && nums[i] <= n && nums[nums[i]-1] !== nums[i])
[nums[i], nums[nums[i]-1]] = [nums[nums[i]-1], nums[i]];
}
for (let i = 0; i < n; i++) if (nums[i] !== i + 1) return i + 1;
return n + 1;
}
Time: O(n) | Space: O(1)
Q15 β Minimum Window Substring
π§ How to Approach This
Step 1 β Recognize the pattern: Sliding window with frequency Maps; expand right, shrink left.
Step 2 β Brute force (what NOT to do): Try all substrings β O(nΒ² Β· m).
Step 3 β Optimal insight:
formedcounter tracks satisfied character requirements.Step 4 β Algorithm:
1. Build need Map from t; expand r; when all formed β shrink l; track min window
Step 5 β Edge cases: t longer than s β ""; t not in s β "".
function minWindow(s, t) {
if (t.length > s.length) return '';
const need = new Map(), have = new Map();
for (const c of t) need.set(c, (need.get(c) ?? 0) + 1);
let formed = 0, required = need.size;
let l = 0, minLen = Infinity, res = '';
for (let r = 0; r < s.length; r++) {
const c = s[r]; have.set(c, (have.get(c) ?? 0) + 1);
if (need.has(c) && have.get(c) === need.get(c)) formed++;
while (formed === required) {
if (r - l + 1 < minLen) { minLen = r - l + 1; res = s.slice(l, r + 1); }
const lc = s[l]; have.set(lc, have.get(lc) - 1);
if (need.has(lc) && have.get(lc) < need.get(lc)) formed--;
l++;
}
}
return res;
}
Time: O(n + m) | Space: O(n + m)
Q16 β Find All Anagrams in a String
π§ How to Approach This
Step 1 β Recognize the pattern: Fixed-size sliding window with frequency tracking.
Step 2 β Brute force (what NOT to do): Sort every window β O(n Β· k log k).
Step 3 β Optimal insight: Slide window; maintain
matchedcounter for O(1) comparison.Step 4 β Algorithm:
1. Init freq maps for first window; compute matched
2. Slide: update right + left; check matched
Step 5 β Edge cases: p longer than s β [].
function findAnagrams(s, p) {
if (s.length < p.length) return [];
const need = {}, have = {}, result = [];
for (const c of p) need[c] = (need[c] ?? 0) + 1;
for (let i = 0; i < p.length; i++) have[s[i]] = (have[s[i]] ?? 0) + 1;
let matched = 0;
for (const c in need) if ((have[c] ?? 0) === need[c]) matched++;
if (matched === Object.keys(need).length) result.push(0);
for (let i = p.length; i < s.length; i++) {
const r = s[i]; have[r] = (have[r] ?? 0) + 1;
if (need[r] !== undefined) { if (have[r] === need[r]) matched++; else if (have[r] === need[r]+1) matched--; }
const l = s[i - p.length]; have[l]--;
if (need[l] !== undefined) { if (have[l] === need[l]) matched++; else if (have[l] === need[l]-1) matched--; }
if (matched === Object.keys(need).length) result.push(i - p.length + 1);
}
return result;
}
Time: O(n) | Space: O(1)
Q17 β Ransom Note
π§ How to Approach This
Step 1 β Recognize the pattern: Frequency Map β magazine must have enough of each char.
Step 2 β Brute force (what NOT to do): For each char in note, scan magazine β O(nΒ·m).
Step 3 β Optimal insight: Count magazine chars; verify note requirements are met.
Step 4 β Algorithm:
1. Build freq Map from magazine
2. For each char in ransomNote: if freq < 1 β false; decrement
Step 5 β Edge cases: Empty ransomNote β true.
function canConstruct(ransomNote, magazine) {
const freq = new Map();
for (const c of magazine) freq.set(c, (freq.get(c) ?? 0) + 1);
for (const c of ransomNote) {
if ((freq.get(c) ?? 0) < 1) return false;
freq.set(c, freq.get(c) - 1);
}
return true;
}
Time: O(n + m) | Space: O(1)
Q18 β Jewels and Stones
π§ How to Approach This
Step 1 β Recognize the pattern: Set for jewel types; count stones that are jewels.
Step 2 β Brute force (what NOT to do): For each stone scan jewels string β O(nΒ·m).
Step 3 β Optimal insight: Set from jewels for O(1) lookup.
Step 4 β Algorithm:
1. jewelSet = new Set(jewels)
2. Count stones in jewelSet
Step 5 β Edge cases: Empty stones β 0.
function numJewelsInStones(jewels, stones) {
const set = new Set(jewels);
return [...stones].filter(s => set.has(s)).length;
}
Time: O(j + s) | Space: O(j)
Q19 β Longest Palindrome from Characters
π§ How to Approach This
Step 1 β Recognize the pattern: Pairs contribute 2; at most one odd-count char as center.
Step 2 β Brute force (what NOT to do): Try all permutations.
Step 3 β Optimal insight: Sum βfreq/2β*2 + 1 if any odd freq exists.
Step 4 β Algorithm:
1. Count freq of each char
2. sum += Math.floor(f/2)*2; track hasOdd
3. Return sum + (hasOdd ? 1 : 0)
Step 5 β Edge cases: Single char β 1.
function longestPalindrome(s) {
const freq = new Map();
for (const c of s) freq.set(c, (freq.get(c) ?? 0) + 1);
let len = 0, hasOdd = false;
for (const f of freq.values()) {
len += Math.floor(f / 2) * 2;
if (f % 2 !== 0) hasOdd = true;
}
return len + (hasOdd ? 1 : 0);
}
Time: O(n) | Space: O(1)
Q20 β Pairs with Given Difference
π§ How to Approach This
Step 1 β Recognize the pattern: For each n, check if n+k exists in Map.
Step 2 β Brute force (what NOT to do): Nested loops β O(nΒ²).
Step 3 β Optimal insight: Frequency Map; for each n: if n+k exists β count pair.
Step 4 β Algorithm:
1. Build freq Map
2. For each key n: if k=0 and freq>1 β count; if k>0 and map.has(n+k) β count
Step 5 β Edge cases: k=0; negative k.
function findPairs(nums, k) {
if (k < 0) return 0;
const freq = new Map(); let count = 0;
for (const n of nums) freq.set(n, (freq.get(n) ?? 0) + 1);
for (const [n, f] of freq) {
if (k === 0) { if (f > 1) count++; }
else if (freq.has(n + k)) count++;
}
return count;
}
Time: O(n) | Space: O(n)
Q21 β Minimum Index Sum of Two Lists
π§ How to Approach This
Step 1 β Recognize the pattern: Map first list index β element; scan second for matches.
Step 2 β Brute force (what NOT to do): Nested loops β O(nΒ·m).
Step 3 β Optimal insight: Map list1 element β index; iterate list2; track min sum.
Step 4 β Algorithm:
1. Map list1: element β index
2. For each (j, item) in list2: if in map, compute i+j; update min; collect ties
Step 5 β Edge cases: Multiple pairs with same min sum.
function findRestaurant(list1, list2) {
const map = new Map(list1.map((v, i) => [v, i]));
let minSum = Infinity, result = [];
for (let j = 0; j < list2.length; j++) {
if (map.has(list2[j])) {
const sum = map.get(list2[j]) + j;
if (sum < minSum) { minSum = sum; result = [list2[j]]; }
else if (sum === minSum) result.push(list2[j]);
}
}
return result;
}
Time: O(n + m) | Space: O(n)
Q22 β Custom Sort String
π§ How to Approach This
Step 1 β Recognize the pattern: Frequency Map of s; output in order string first, then remaining.
Step 2 β Brute force (what NOT to do): Custom comparator sort β O(n log n).
Step 3 β Optimal insight: Count freq; output ordered chars first, then leftover.
Step 4 β Algorithm:
1. Build freq Map of s
2. For each char in order: append freq[c] times; delete from map
3. Append remaining chars
Step 5 β Edge cases: Chars in s not in order.
function customSortString(order, s) {
const freq = new Map();
for (const c of s) freq.set(c, (freq.get(c) ?? 0) + 1);
let result = '';
for (const c of order) {
if (freq.has(c)) { result += c.repeat(freq.get(c)); freq.delete(c); }
}
for (const [c, f] of freq) result += c.repeat(f);
return result;
}
Time: O(n) | Space: O(n)
Q23 β Count Pairs Divisible by K
π§ How to Approach This
Step 1 β Recognize the pattern: Remainder Map β (a+b)%k=0 when complements match.
Step 2 β Brute force (what NOT to do): Nested loops β O(nΒ²).
Step 3 β Optimal insight: For each n, need = (k - n%k) % k; check remMap.
Step 4 β Algorithm:
1. For each n: need = (k - n%k) % k; count += remMap.get(need) ?? 0
2. Then add n%k to remMap
Step 5 β Edge cases: n%k=0 β need=0; k=1 β all pairs.
function countPairs(nums, k) {
const remMap = new Map(); let count = 0;
for (const n of nums) {
const rem = n % k;
const need = (k - rem) % k;
count += remMap.get(need) ?? 0;
remMap.set(rem, (remMap.get(rem) ?? 0) + 1);
}
return count;
}
Time: O(n) | Space: O(k)
Q24 β Number of Boomerangs
π§ How to Approach This
Step 1 β Recognize the pattern: For each anchor, count others at same distance; ordered pairs = f*(f-1).
Step 2 β Brute force (what NOT to do): Triple nested loop β O(nΒ³).
Step 3 β Optimal insight: For each point as anchor: build distMap; count += f*(f-1).
Step 4 β Algorithm:
1. For each point as anchor: build distance β count Map
2. For each freq f: count += f*(f-1) (ordered pairs)
Step 5 β Edge cases: n < 3 β 0.
function numberOfBoomerangs(points) {
let count = 0;
for (const p of points) {
const distMap = new Map();
for (const q of points) {
const d = (p[0]-q[0])**2 + (p[1]-q[1])**2;
distMap.set(d, (distMap.get(d) ?? 0) + 1);
}
for (const f of distMap.values()) count += f * (f - 1);
}
return count;
}
Time: O(nΒ²) | Space: O(n)
Q25 β Longest Subarray with Sum Divisible by K
π§ How to Approach This
Step 1 β Recognize the pattern: Prefix sum mod k; same remainder at two indices = divisible subarray.
Step 2 β Brute force (what NOT to do): All subarrays β O(nΒ²).
Step 3 β Optimal insight: Map first occurrence of each remainder; max length = i - firstOccurrence[rem].
Step 4 β Algorithm:
1. remMap.set(0, -1); running sum %= k (normalize negative)
2. If rem seen β max(best, i - remMap.get(rem)); else remMap.set(rem, i)
Step 5 β Edge cases: Negative numbers; no valid subarray β 0.
function subarraysDivByK(nums, k) {
const remMap = new Map([[0, -1]]);
let sum = 0, best = 0;
for (let i = 0; i < nums.length; i++) {
sum += nums[i];
const rem = ((sum % k) + k) % k;
if (remMap.has(rem)) best = Math.max(best, i - remMap.get(rem));
else remMap.set(rem, i);
}
return best;
}
Time: O(n) | Space: O(k)