AI InterviewSearch…Search problems, topics, or pages…Ctrl KGet started
Skip to content

20 Interview idioms / Lesson 4 of 6

The collections idiom sheet

15 min · +10 XP · 2 exercises · 6 questions

Read the text15 min
Exercises0/2
Quiz6 q

Quick answer

How do you find the top k frequent elements in Java?

To find the top k frequent elements in Java, count occurrences with HashMap.merge(key, 1, Integer::sum), then offer the entries to a PriorityQueue ordered by count — a min-heap — and poll whenever its size exceeds k. The heap keeps the k largest in O(n log k) time; poll them out and reverse for descending order, breaking ties in the comparator.

Interview problems repeat. Under the surface of "find the k most frequent", "longest substring without repeats", "merge intervals" and "next greater element" are perhaps fifteen shapes, and each has a two-to-six-line Java idiom that experienced candidates type without thinking. This lesson is the sheet: the idiom, the structure it uses, the complexity, and the trap. Learn them as vocabulary — you should be able to say "frequency map, then a min-heap of size k" as one phrase.

Counting and grouping

Map<String, Integer> freq = new HashMap<>();
for (String w : words) freq.merge(w, 1, Integer::sum);                       // O(n); the frequency map
Map<Integer, List<String>> byLen = new HashMap<>();
for (String w : words) byLen.computeIfAbsent(w.length(), k -> new ArrayList<>()).add(w);   // grouping
int[] count = new int[26]; for (char c : s.toCharArray()) count[c - 'a']++;  // bounded alphabet: an array beats a map

Trap: boxed == when comparing counts; an alphabet array is both faster and immune.

Top-k and ordering by a key

PriorityQueue<Map.Entry<String, Integer>> heap =                             // min-heap on count, size k
    new PriorityQueue<>(Map.Entry.comparingByValue());
for (var e : freq.entrySet()) { heap.offer(e); if (heap.size() > k) heap.poll(); }   // O(n log k)
// heap now holds the k largest; poll to list them ascending, reverse for descending

Use a min-heap to keep the largest k (evict the smallest). For ties, put the secondary key in the comparator (comparingByValue().thenComparing(Map.Entry.comparingByKey(Comparator.reverseOrder())) so that alphabetically earlier words survive). For k close to n, sorting is simpler and just as fast. PriorityQueue has no decreaseKey: for Dijkstra, push duplicates and skip stale entries on poll.

Stacks, queues, deques

Deque<Integer> stack = new ArrayDeque<>(); stack.push(x); stack.pop(); stack.peek();
Deque<Integer> queue = new ArrayDeque<>(); queue.offer(x); queue.poll(); queue.peek();

Never Stack (synchronised, a Vector) or LinkedList (slow, allocation-heavy). ArrayDeque rejects null — use a sentinel or Integer box carefully. Monotonic stack for "next greater element" / histogram problems:

Deque<Integer> idx = new ArrayDeque<>();                  // indexes with decreasing values
for (int i = 0; i < n; i++) {
    while (!idx.isEmpty() && a[idx.peek()] < a[i]) next[idx.pop()] = i;   // a[i] is the next greater for those
    idx.push(i);
}

Sliding-window maximum is the same idea with a deque that also drops indexes that left the window from the front — O(n).

Sorted structures: TreeMap navigation

TreeMap<Integer, String> tm = …;
tm.floorKey(x); tm.ceilingKey(x); tm.lowerKey(x); tm.higherKey(x);    // nearest keys, O(log n)
tm.firstKey(); tm.lastKey(); tm.headMap(x); tm.tailMap(x, true); tm.subMap(lo, hi);
tm.pollFirstEntry();                                                    // sorted queue with removal by key

Interval scheduling ("is this slot free?"), nearest-value queries, "count of elements ≤ x" (via headMap(x, true).size() — O(n), so use a Fenwick tree if it is hot), and an ordered multiset via TreeMap<Integer, Integer> counts.

Two pointers and sliding windows

int lo = 0; Map<Character, Integer> last = new HashMap<>(); int best = 0;
for (int hi = 0; hi < s.length(); hi++) {
    char c = s.charAt(hi);
    if (last.containsKey(c) && last.get(c) >= lo) lo = last.get(c) + 1;     // shrink past the repeat
    last.put(c, hi);
    best = Math.max(best, hi - lo + 1);                                       // longest substring without repeats
}

Windows are for contiguous subarrays with a monotone condition; two pointers from both ends for sorted arrays (pair sums). Both O(n). Trap: shrinking with while when the condition is not monotone — check that moving lo right can only help.

Prefix sums and hashing for subarrays

long[] pre = new long[n + 1]; for (int i = 0; i < n; i++) pre[i + 1] = pre[i] + a[i];   // sum(i..j) = pre[j+1] - pre[i]
Map<Long, Integer> seen = new HashMap<>(); seen.put(0L, 1); long run = 0; int count = 0;
for (int x : a) { run += x; count += seen.getOrDefault(run - target, 0); seen.merge(run, 1, Integer::sum); }   // subarrays summing to target, O(n)

long prefixes always. 2-D prefix sums for grid rectangles; XOR prefixes for XOR subarrays.

Hash-set tricks

Set<Integer> seen = new HashSet<>();
for (int x : a) { if (seen.contains(target - x)) return true; seen.add(x); }    // two-sum, O(n)
new HashSet<>(list).size() == list.size();                                      // all distinct?
Set<Integer> common = new HashSet<>(aList); common.retainAll(bList);            // intersection

Anagram grouping: key = sorted characters (new String(sortedChars)) or a count signature; groupingBy on that key.

Graph skeletons

List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i < n; i++) adj.add(new ArrayList<>());   // adjacency list
// BFS
Deque<Integer> q = new ArrayDeque<>(); int[] dist = new int[n]; Arrays.fill(dist, -1);
dist[src] = 0; q.offer(src);
while (!q.isEmpty()) { int u = q.poll(); for (int v : adj.get(u)) if (dist[v] < 0) { dist[v] = dist[u] + 1; q.offer(v); } }
// DFS, iterative (no stack overflow)
Deque<Integer> st = new ArrayDeque<>(); boolean[] seen = new boolean[n]; st.push(src);
while (!st.isEmpty()) { int u = st.pop(); if (seen[u]) continue; seen[u] = true; for (int v : adj.get(u)) if (!seen[v]) st.push(v); }
// Dijkstra
PriorityQueue<long[]> pq = new PriorityQueue<>(Comparator.comparingLong(x -> x[0]));   // {dist, node}

Grid graphs: int[] dr = {1, -1, 0, 0}, dc = {0, 0, 1, -1} and a bounds check. int[][] for the grid, boolean[][] for visited — not sets of strings.

Memoisation and DP tables

Map<Long, Long> memo = new HashMap<>();                     // key: encode (i, j) as i * M + j — cheaper than a String or a List key
long[][] dp = new long[n + 1][m + 1];  Arrays.fill(dp[0], INF);                  // INF = Long.MAX_VALUE / 4 so INF + INF does not overflow

Prefer arrays to maps for DP over small integer states; encode multi-dimensional keys into one long; choose an INF that survives addition.

Bits

Integer.bitCount(x); Integer.highestOneBit(x); Integer.numberOfTrailingZeros(x);
(mask >> i & 1) == 1;  mask | (1 << i);  mask & ~(1 << i);  mask & (mask - 1);   // test, set, clear, drop lowest set bit
for (int sub = mask; sub > 0; sub = (sub - 1) & mask) { … }                      // enumerate submasks

1 << i is an int — 1L << i for i ≥ 31. >>> for unsigned shifts.

String building and manipulation

new StringBuilder(s).reverse().toString();
sb.setLength(sb.length() - 1);                              // drop the trailing separator
sb.insert(0, c); sb.deleteCharAt(i); sb.setCharAt(i, c);
String.join(",", list); String.valueOf(chars); new String(chars, from, len);
Character.isLetterOrDigit(c); Character.toLowerCase(c); Character.getNumericValue(c);

The sheet, as sentences

Problem shapeSayStructureComplexity
count thingsfrequency map / alphabet arrayHashMap.merge, int[26]O(n)
k largest / most frequentmin-heap of size kPriorityQueueO(n log k)
next greater / histogrammonotonic stackArrayDequeO(n)
window max/minmonotonic dequeArrayDequeO(n)
nearest / range queries on sorted keystree map navigationTreeMap.floorKeyO(log n)
contiguous subarray with monotone predicatesliding windowtwo indexesO(n)
subarray sumsprefix sums + hash maplong[], HashMapO(n)
pair with propertyhash set / two pointersHashSet / sorted arrayO(n) / O(n log n)
shortest path unweighted / weightedBFS / DijkstraArrayDeque / PriorityQueueO(V + E) / O(E log V)
overlapping subproblemsmemo / DP tablelong[][], encoded long keysstates × transitions
subsets of ≤ 20bitmaskint masksO(2ⁿ · n)

Interview angle

  • "k most frequent words?" Frequency map, min-heap of size k keyed on count (ties by word), poll and reverse — O(n log k).
  • "Longest substring without repeating characters?" Sliding window with last-seen indexes — O(n).
  • "Count subarrays summing to k?" Running prefix sum and a map of prefix counts — O(n).
  • "Why ArrayDeque and not Stack?" Stack is a synchronised Vector with the wrong API; ArrayDeque is faster and idiomatic.
  • "Deep DFS in Java?" Iterative with an explicit stack to avoid StackOverflowError.

Key takeaways

  • Fifteen shapes; name the shape, then the structure, then the complexity — before writing code.
  • merge/computeIfAbsent for counting and grouping; int[26] when the alphabet is small.
  • Min-heap of size k for top-k; monotonic stack/deque for next-greater and window extremes; TreeMap navigation for sorted queries.
  • Prefix sums in long; hash sets for pairs; two pointers for sorted input; iterative BFS/DFS on adjacency lists.
  • Encode DP keys as long; pick INF that survives addition; 1L << i past 31.

Common questions

Why use ArrayDeque instead of Stack in Java?

Stack is a legacy class that extends the synchronised Vector; ArrayDeque is faster and the idiomatic choice — push, pop and peek as a stack, offer, poll and peek as a queue. It does not accept null elements.

What is a monotonic stack?

A stack of indexes kept in order of their values, used for next-greater-element and histogram problems. For each new element, pop every index whose value is smaller — the new element is their next greater — then push it. Each index is pushed and popped once, so the pass is O(n).

How do you count subarrays that sum to k in Java?

Keep a running prefix sum in a long and a HashMap from each prefix sum to how often it has occurred, starting with 0 seen once. At each element add the count of run - k to the answer, then record run. The whole pass is O(n).

How do you find the longest substring without repeating characters?

Use a sliding window: advance hi along the string, remember each character's last index in a map, and when the current character was last seen inside the window, move lo just past that position. The largest hi - lo + 1 is the answer, found in O(n).

What is TreeMap floorKey used for?

floorKey(x) returns the largest key less than or equal to x, and ceilingKey, lowerKey and higherKey give the other nearest keys, each in O(log n). They answer nearest-value and interval questions, such as whether a time slot is still free.

Mark as readSign in to keep your place.

Java Collections for Coding Interviews: Heaps, Deques, TreeMap

Java idioms behind common interview problems: frequency maps, top-k heaps, monotonic stacks, sliding windows, prefix sums, TreeMap, BFS and bitmasks.

  • Course: Java study plan
  • Module: Interview idioms
  • Kind: Lesson
  • Reading time: 15 min
  • Runtime: OpenJDK 18

How do you find the top k frequent elements in Java?

To find the top k frequent elements in Java, count occurrences with HashMap.merge(key, 1, Integer::sum), then offer the entries to a PriorityQueue ordered by count — a min-heap — and poll whenever its size exceeds k. The heap keeps the k largest in O(n log k) time; poll them out and reverse for descending order, breaking ties in the comparator.

Lesson

Interview problems repeat. Under the surface of "find the k most frequent", "longest substring without repeats", "merge intervals" and "next greater element" are perhaps fifteen shapes, and each has a two-to-six-line Java idiom that experienced candidates type without thinking. This lesson is the sheet: the idiom, the structure it uses, the complexity, and the trap. Learn them as vocabulary — you should be able to say "frequency map, then a min-heap of size k" as one phrase.

Counting and grouping

Map<String, Integer> freq = new HashMap<>();
for (String w : words) freq.merge(w, 1, Integer::sum);                       // O(n); the frequency map
Map<Integer, List<String>> byLen = new HashMap<>();
for (String w : words) byLen.computeIfAbsent(w.length(), k -> new ArrayList<>()).add(w);   // grouping
int[] count = new int[26]; for (char c : s.toCharArray()) count[c - 'a']++;  // bounded alphabet: an array beats a map

Trap: boxed == when comparing counts; an alphabet array is both faster and immune.

Top-k and ordering by a key

PriorityQueue<Map.Entry<String, Integer>> heap =                             // min-heap on count, size k
    new PriorityQueue<>(Map.Entry.comparingByValue());
for (var e : freq.entrySet()) { heap.offer(e); if (heap.size() > k) heap.poll(); }   // O(n log k)
// heap now holds the k largest; poll to list them ascending, reverse for descending

Use a min-heap to keep the largest k (evict the smallest). For ties, put the secondary key in the comparator (comparingByValue().thenComparing(Map.Entry.comparingByKey(Comparator.reverseOrder())) so that alphabetically earlier words survive). For k close to n, sorting is simpler and just as fast. PriorityQueue has no decreaseKey: for Dijkstra, push duplicates and skip stale entries on poll.

Stacks, queues, deques

Deque<Integer> stack = new ArrayDeque<>(); stack.push(x); stack.pop(); stack.peek();
Deque<Integer> queue = new ArrayDeque<>(); queue.offer(x); queue.poll(); queue.peek();

Never Stack (synchronised, a Vector) or LinkedList (slow, allocation-heavy). ArrayDeque rejects null — use a sentinel or Integer box carefully. Monotonic stack for "next greater element" / histogram problems:

Deque<Integer> idx = new ArrayDeque<>();                  // indexes with decreasing values
for (int i = 0; i < n; i++) {
    while (!idx.isEmpty() && a[idx.peek()] < a[i]) next[idx.pop()] = i;   // a[i] is the next greater for those
    idx.push(i);
}

Sliding-window maximum is the same idea with a deque that also drops indexes that left the window from the front — O(n).

Sorted structures: TreeMap navigation

TreeMap<Integer, String> tm = …;
tm.floorKey(x); tm.ceilingKey(x); tm.lowerKey(x); tm.higherKey(x);    // nearest keys, O(log n)
tm.firstKey(); tm.lastKey(); tm.headMap(x); tm.tailMap(x, true); tm.subMap(lo, hi);
tm.pollFirstEntry();                                                    // sorted queue with removal by key

Interval scheduling ("is this slot free?"), nearest-value queries, "count of elements ≤ x" (via headMap(x, true).size() — O(n), so use a Fenwick tree if it is hot), and an ordered multiset via TreeMap<Integer, Integer> counts.

Two pointers and sliding windows

int lo = 0; Map<Character, Integer> last = new HashMap<>(); int best = 0;
for (int hi = 0; hi < s.length(); hi++) {
    char c = s.charAt(hi);
    if (last.containsKey(c) && last.get(c) >= lo) lo = last.get(c) + 1;     // shrink past the repeat
    last.put(c, hi);
    best = Math.max(best, hi - lo + 1);                                       // longest substring without repeats
}

Windows are for contiguous subarrays with a monotone condition; two pointers from both ends for sorted arrays (pair sums). Both O(n). Trap: shrinking with while when the condition is not monotone — check that moving lo right can only help.

Prefix sums and hashing for subarrays

long[] pre = new long[n + 1]; for (int i = 0; i < n; i++) pre[i + 1] = pre[i] + a[i];   // sum(i..j) = pre[j+1] - pre[i]
Map<Long, Integer> seen = new HashMap<>(); seen.put(0L, 1); long run = 0; int count = 0;
for (int x : a) { run += x; count += seen.getOrDefault(run - target, 0); seen.merge(run, 1, Integer::sum); }   // subarrays summing to target, O(n)

long prefixes always. 2-D prefix sums for grid rectangles; XOR prefixes for XOR subarrays.

Hash-set tricks

Set<Integer> seen = new HashSet<>();
for (int x : a) { if (seen.contains(target - x)) return true; seen.add(x); }    // two-sum, O(n)
new HashSet<>(list).size() == list.size();                                      // all distinct?
Set<Integer> common = new HashSet<>(aList); common.retainAll(bList);            // intersection

Anagram grouping: key = sorted characters (new String(sortedChars)) or a count signature; groupingBy on that key.

Graph skeletons

List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i < n; i++) adj.add(new ArrayList<>());   // adjacency list
// BFS
Deque<Integer> q = new ArrayDeque<>(); int[] dist = new int[n]; Arrays.fill(dist, -1);
dist[src] = 0; q.offer(src);
while (!q.isEmpty()) { int u = q.poll(); for (int v : adj.get(u)) if (dist[v] < 0) { dist[v] = dist[u] + 1; q.offer(v); } }
// DFS, iterative (no stack overflow)
Deque<Integer> st = new ArrayDeque<>(); boolean[] seen = new boolean[n]; st.push(src);
while (!st.isEmpty()) { int u = st.pop(); if (seen[u]) continue; seen[u] = true; for (int v : adj.get(u)) if (!seen[v]) st.push(v); }
// Dijkstra
PriorityQueue<long[]> pq = new PriorityQueue<>(Comparator.comparingLong(x -> x[0]));   // {dist, node}

Grid graphs: int[] dr = {1, -1, 0, 0}, dc = {0, 0, 1, -1} and a bounds check. int[][] for the grid, boolean[][] for visited — not sets of strings.

Memoisation and DP tables

Map<Long, Long> memo = new HashMap<>();                     // key: encode (i, j) as i * M + j — cheaper than a String or a List key
long[][] dp = new long[n + 1][m + 1];  Arrays.fill(dp[0], INF);                  // INF = Long.MAX_VALUE / 4 so INF + INF does not overflow

Prefer arrays to maps for DP over small integer states; encode multi-dimensional keys into one long; choose an INF that survives addition.

Bits

Integer.bitCount(x); Integer.highestOneBit(x); Integer.numberOfTrailingZeros(x);
(mask >> i & 1) == 1;  mask | (1 << i);  mask & ~(1 << i);  mask & (mask - 1);   // test, set, clear, drop lowest set bit
for (int sub = mask; sub > 0; sub = (sub - 1) & mask) { … }                      // enumerate submasks

1 << i is an int — 1L << i for i ≥ 31. >>> for unsigned shifts.

String building and manipulation

new StringBuilder(s).reverse().toString();
sb.setLength(sb.length() - 1);                              // drop the trailing separator
sb.insert(0, c); sb.deleteCharAt(i); sb.setCharAt(i, c);
String.join(",", list); String.valueOf(chars); new String(chars, from, len);
Character.isLetterOrDigit(c); Character.toLowerCase(c); Character.getNumericValue(c);

The sheet, as sentences

Problem shapeSayStructureComplexity
count thingsfrequency map / alphabet arrayHashMap.merge, int[26]O(n)
k largest / most frequentmin-heap of size kPriorityQueueO(n log k)
next greater / histogrammonotonic stackArrayDequeO(n)
window max/minmonotonic dequeArrayDequeO(n)
nearest / range queries on sorted keystree map navigationTreeMap.floorKeyO(log n)
contiguous subarray with monotone predicatesliding windowtwo indexesO(n)
subarray sumsprefix sums + hash maplong[], HashMapO(n)
pair with propertyhash set / two pointersHashSet / sorted arrayO(n) / O(n log n)
shortest path unweighted / weightedBFS / DijkstraArrayDeque / PriorityQueueO(V + E) / O(E log V)
overlapping subproblemsmemo / DP tablelong[][], encoded long keysstates × transitions
subsets of ≤ 20bitmaskint masksO(2ⁿ · n)

Interview angle

  • "k most frequent words?" Frequency map, min-heap of size k keyed on count (ties by word), poll and reverse — O(n log k).
  • "Longest substring without repeating characters?" Sliding window with last-seen indexes — O(n).
  • "Count subarrays summing to k?" Running prefix sum and a map of prefix counts — O(n).
  • "Why ArrayDeque and not Stack?" Stack is a synchronised Vector with the wrong API; ArrayDeque is faster and idiomatic.
  • "Deep DFS in Java?" Iterative with an explicit stack to avoid StackOverflowError.

Key takeaways

  • Fifteen shapes; name the shape, then the structure, then the complexity — before writing code.
  • merge/computeIfAbsent for counting and grouping; int[26] when the alphabet is small.
  • Min-heap of size k for top-k; monotonic stack/deque for next-greater and window extremes; TreeMap navigation for sorted queries.
  • Prefix sums in long; hash sets for pairs; two pointers for sorted input; iterative BFS/DFS on adjacency lists.
  • Encode DP keys as long; pick INF that survives addition; 1L << i past 31.

Common questions

Why use ArrayDeque instead of Stack in Java?

Stack is a legacy class that extends the synchronised Vector; ArrayDeque is faster and the idiomatic choice — push, pop and peek as a stack, offer, poll and peek as a queue. It does not accept null elements.

What is a monotonic stack?

A stack of indexes kept in order of their values, used for next-greater-element and histogram problems. For each new element, pop every index whose value is smaller — the new element is their next greater — then push it. Each index is pushed and popped once, so the pass is O(n).

How do you count subarrays that sum to k in Java?

Keep a running prefix sum in a long and a HashMap from each prefix sum to how often it has occurred, starting with 0 seen once. At each element add the count of run - k to the answer, then record run. The whole pass is O(n).

How do you find the longest substring without repeating characters?

Use a sliding window: advance hi along the string, remember each character's last index in a map, and when the current character was last seen inside the window, move lo just past that position. The largest hi - lo + 1 is the answer, found in O(n).

What is TreeMap floorKey used for?

floorKey(x) returns the largest key less than or equal to x, and ceilingKey, lowerKey and higherKey give the other nearest keys, each in O(log n). They answer nearest-value and interval questions, such as whether a time slot is still free.

Exercises

Top-k frequent words

Read an integer n, n words, and k. Count with a frequency map, then keep the k most frequent words with a min-heap of size k (PriorityQueue ordered by count ascending, ties by word descending, so the alphabetically earlier word survives a tie). Print the survivors most frequent first, ties alphabetical, as <word> <count>.

Example: 7 then b a c a b a d then 2 →

a 3
b 2

Sliding-window maximum

Read an integer n, n integers, and a window size w (1 ≤ w ≤ n). Print the maximum of every window of w consecutive elements, space-separated, in O(n) using a monotonic deque of indexes (values decreasing from front to back; drop indexes that leave the window from the front).

Example: 8 then 1 3 -1 -3 5 3 6 7 then 3 →

3 3 5 5 6 7

In this module: Interview idioms

← The pitfalls that fail interviews · Writing clean solutions under time pressure →