Interview Handbook by Sayed Elmahdy العربية

Topic 29 · Problem solving

Coding problems, solved

Classic interview problems with explained C# solutions, complexity and thinking aloud.

7 questionsJunior · 4Mid · 2Senior · 18 quiz

Concepts

What you will learn: Turn interview prompts into precise problems, choose a pattern and test it against examples and edge cases. Store and scenarios connect data structures to decisions in everyday software.

Before you start: Review arrays, hash maps, stacks and queues from the previous chapter, or begin with the six-step method. Each card states a contract: input, expected result and constraints. A pattern is a starting hypothesis; check its correctness, complexity and limits before trusting the solution.

Select a term with a dotted underline for its meaning, then continue reading from the same place.

#A six-step interview method

The goal is a correct solution with an explanation of why it works, not memorized code. Explain meaningful decisions as you work.

  1. Restate the task and clarify constraints: empty input, negatives, duplicates, numeric bounds, mutation and the no-solution result.
  2. Write a normal example and boundary cases and agree the output.
  3. Describe brute force and its complexity as a baseline.
  4. Improve it using a clear or recurrence, explaining memory trade-offs.
  5. Code with meaningful names and explain decisions without narrating every keystroke.
  6. examples and edge cases; report time, auxiliary space and output space.
Examples are independent 10 and C# 14 snippets, not one file to paste together. Add System, System.Collections.Generic, System.Linq, System.Text, System.Threading, System.Threading.Tasks and System.IO as needed. Inputs are non-null unless stated; indices and dimensions satisfy the stated contracts. Hash costs are expected. String algorithms operate on -16 chars or specified , not grapheme clusters.

Arrays and hashing

#1. Two Sum

Problem: Return zero-based indices of two distinct elements totaling target, or an empty array. [2,7,11,15], 9 → [0,1]. Idea: Store earlier values and look for the complement instead of testing every pair in O(n²).

Try it before opening the solution: input [2,7,11,15] with target = 9 should return [0,1]. Also test an empty array, no solution, and repeated values such as [3,3], 6.

Show worked solution
int[] TwoSum(int[] values, int target)
{
    var seen = new Dictionary<long, int>();
    for (int i = 0; i < values.Length; i++)
    {
        long need = (long)target - values[i]; // (1)
        if (seen.TryGetValue(need, out int previous)) return [previous, i];
        seen[values[i]] = i; // (2)
    }
    return [];
}
🧾 Code walkthrough
  1. (1) long prevents overflow when subtracting int values.
  2. (2) Store after lookup to avoid using one element twice; [3,3], 6 works.

Complexity: Expected O(n) time, O(n) space. Sorted inputs allow O(1)-space two pointers for their indices; sorting otherwise changes original indices unless tracked.

#2. Duplicates and first unique character

Problems: Detect a repeated number, and find the first non-repeating character index or -1. Examples: [1,2,1] → true, "aabbc" → 4. Idea: Use membership for duplicates and frequency followed by ordered scanning for uniqueness. The character example accepts lowercase English only.

bool ContainsDuplicate(int[] values)
{
    var seen = new HashSet<int>();
    foreach (int value in values)
        if (!seen.Add(value)) return true; // (1)
    return false;
}
int FirstUniqueChar(string text)
{
    var count = new int[26];
    foreach (char c in text) count[c - 'a']++;
    for (int i = 0; i < text.Length; i++)
        if (count[text[i] - 'a'] == 1) return i; // (2)
    return -1;
}
🧾 Code walkthrough
  1. (1) Add returning false means the value already exists.
  2. (2) The second scan preserves text order rather than dictionary order.

Complexity: Duplicate detection is expected O(n) time and O(u) space for distinct values. Unique-character search is O(n) time and O(1) space for the fixed alphabet. Test empty and fully repeated inputs; broader text requires a different counter and an agreed character definition.

#3. Valid anagram and group anagrams

Problems: Check equal character frequencies and group matching words. listen and silent are anagrams; [eat,tea,tan] → [[eat,tea],[tan]]. Idea: Use counts for lowercase-English validation and sorted char signatures for case-sensitive grouping.

bool IsAnagram(string first, string second)
{
    if (first.Length != second.Length) return false;
    var count = new int[26];
    for (int i = 0; i < first.Length; i++)
    {
        count[first[i] - 'a']++;
        count[second[i] - 'a']--; // (1)
    }
    return count.All(value => value == 0);
}
List<List<string>> GroupAnagrams(string[] words)
{
    var groups = new Dictionary<string, List<string>>(StringComparer.Ordinal);
    foreach (string word in words)
    {
        char[] letters = word.ToCharArray();
        Array.Sort(letters);
        string key = new(letters); // (2)
        if (!groups.TryGetValue(key, out var group)) groups[key] = group = [];
        group.Add(word);
    }
    return groups.Values.ToList();
}
🧾 Code walkthrough
  1. (1) Increments and decrements cancel exactly for matching frequencies.
  2. (2) Anagrams share a sorted key; group order is not part of the contract.

Complexity: Validation takes O(n) time and O(1) space. Grouping takes expected O(w × k log k) time for w words of maximum length k, with O(wk) upper-bound storage for signatures and groups. A count signature avoids sorting for a fixed alphabet. Test empty strings and repeated words.

#4. Top K frequent elements

Problem: Return the k most frequent values; [1,1,1,2,2,3], 2 → [1,2]. Idea: Count frequencies and keep a size-k min-heap. Any tied selection is valid; result order is unspecified. k must be between zero and the distinct count.

int[] TopKFrequent(int[] values, int k)
{
    var counts = new Dictionary<int, int>();
    foreach (int value in values)
        counts[value] = counts.GetValueOrDefault(value) + 1; // (1)
    if (k < 0 || k > counts.Count) throw new ArgumentOutOfRangeException(nameof(k));
    if (k == 0) return [];
    var heap = new PriorityQueue<int, int>();
    foreach (var (value, count) in counts)
    {
        heap.Enqueue(value, count);
        if (heap.Count > k) heap.Dequeue(); // (2)
    }
    var result = new int[k];
    for (int i = 0; i < k; i++) result[i] = heap.Dequeue();
    return result;
}
🧾 Code walkthrough
  1. (1) Count once instead of recounting every value.
  2. (2) Removing the lowest frequency retains the strongest k candidates.

Complexity: Expected O(n + u log(k + 1)) time and O(u + k) space, where u is distinct count. Sorting counts is simpler but costs O(u log u). Frequency buckets give expected O(n) time with O(n + u) space. Test k=0, k=u and ties.

#5. Product of array except self

Problem: Return the product of all other elements without division; [1,2,3,4] → [24,12,8,6]. Idea: Multiply the prefix before each position by the suffix after it. All intermediate products must fit long; checked detects violations.

long[] ProductExceptSelf(int[] values)
{
    var result = new long[values.Length];
    long prefix = 1;
    for (int i = 0; i < values.Length; i++)
    {
        result[i] = prefix; // (1)
        if (i < values.Length - 1) prefix = checked(prefix * values[i]);
    }
    long suffix = 1;
    for (int i = values.Length - 1; i >= 0; i--)
    {
        result[i] = checked(result[i] * suffix); // (2)
        if (i > 0) suffix = checked(suffix * values[i]);
    }
    return result;
}
🧾 Code walkthrough
  1. (1) result[i] initially holds only the product before i.
  2. (2) suffix holds only elements after i; zeros need no division special case.

Complexity: O(n) time, O(1) auxiliary space excluding O(n) output. Empty input returns empty; a singleton returns the empty product, 1. Test one zero, two zeros and negatives. BigInteger supports larger values but multiplication is no longer constant-cost.

#6. Maximum subarray and stock profit

Problems: Find the maximum nonempty contiguous subarray sum, and the best single buy-then-sell profit for non-negative daily prices. Examples: [-2,1,-3,4,-1,2,1] → 6, [7,1,5,3,6,4] → 5. Idea: Track the best sum ending here, or the lowest earlier price.

Try it before opening the solution: the maximum subarray sum for [-2,1,-3,4,-1,2,1,-5,4] is 6, and the best profit for prices [7,1,5,3,6,4] is 5. Also test all-negative values (return the least-negative value) and descending or empty prices (profit is zero).

Show worked solution
long MaxSubarray(int[] values)
{
    if (values.Length == 0) throw new ArgumentException("Nonempty input required");
    long current = values[0], best = current;
    for (int i = 1; i < values.Length; i++)
    {
        current = Math.Max(values[i], current + values[i]); // (1)
        best = Math.Max(best, current);
    }
    return best;
}
long MaxProfit(int[] prices)
{
    int minimum = int.MaxValue;
    long best = 0;
    foreach (int price in prices)
    {
        minimum = Math.Min(minimum, price);
        best = Math.Max(best, (long)price - minimum); // (2)
    }
    return best;
}
🧾 Code walkthrough
  1. (1) Extend the run or restart at this value; initializing from the first value handles all-negative input.
  2. (2) Compare today's sale with the lowest price seen; choosing not to trade earns zero.

Complexity: Both take O(n) time and O(1) space. Kadane returns -2 for [-5,-2], not zero; descending or empty prices return zero profit. Testing every range or pair of days is quadratic or worse if sums are recomputed.

#7. In-place array patterns

Problems: Move zeros to the end while preserving other order; find the missing number among distinct values from 0 through n; compact a sorted array and return its unique-prefix length. Examples: [0,1,0,3] → [1,3,0,0], [3,0,1] → 2, [1,1,2] → length 2.

void MoveZeroes(int[] values)
{
    int write = 0;
    for (int read = 0; read < values.Length; read++)
        if (values[read] != 0) values[write++] = values[read]; // (1)
    while (write < values.Length) values[write++] = 0;
}
int MissingNumber(int[] values)
{
    int missing = values.Length;
    for (int i = 0; i < values.Length; i++) missing ^= i ^ values[i]; // (2)
    return missing;
}
int RemoveDuplicates(int[] sorted)
{
    if (sorted.Length == 0) return 0;
    int write = 1;
    for (int read = 1; read < sorted.Length; read++)
        if (sorted[read] != sorted[write - 1]) sorted[write++] = sorted[read]; // (3)
    return write;
}
🧾 Code walkthrough
  1. (1) write never overtakes read, so unread input is preserved.
  2. (2) cancels matching indices and values without the overflow risk of summing 0..n.
  3. (3) Compare against the last written value; positions beyond the returned length are unspecified.

Idea and complexity: Read/write pointers or XOR; all three use O(n) time and O(1) extra space. The first and third mutate input; the second requires distinct in-range values. Test empty, all-zero and all-equal inputs; missing-number returns 0 for empty input.

#8. Merge intervals

Problem: Merge overlapping closed intervals; [[1,3],[2,6],[8,10]] → [[1,6],[8,10]]. Starts do not exceed ends. Idea: Sort by start and compare with the latest result. Touching endpoints merge here; half-open booking intervals may require a different rule.

int[][] MergeIntervals(int[][] intervals)
{
    var sorted = intervals.OrderBy(item => item[0]).ToArray();
    var result = new List<int[]>();
    foreach (var item in sorted)
    {
        if (result.Count == 0 || item[0] > result[^1][1])
            result.Add([item[0], item[1]]); // (1)
        else result[^1][1] = Math.Max(result[^1][1], item[1]); // (2)
    }
    return result.ToArray();
}
🧾 Code walkthrough
  1. (1) Copy endpoints to avoid mutating original arrays. Empty input returns empty.
  2. (2) Sorted starts make comparison with the last merged interval sufficient; Math.Max handles nesting.

Complexity: O(n log n) time and O(n) space for sorting and output. Test touching, nested and unsorted intervals. Pairwise overlap handling is more expensive and harder to maintain than the sorted scan.

Strings, two pointers and sliding windows

#9. Palindromes and string reversal

Problems: Check a palindrome ignoring case and non-alphanumeric chars, reverse a char array in place, and reverse space-separated words. "A man, a plan, a canal: Panama" → true; " hello world " → "world hello". Idea: Two pointers from the ends, or split and reverse words.

bool IsPalindrome(string text)
{
    int left = 0, right = text.Length - 1;
    while (left < right)
    {
        if (!char.IsLetterOrDigit(text[left])) { left++; continue; }
        if (!char.IsLetterOrDigit(text[right])) { right--; continue; }
        if (char.ToLowerInvariant(text[left]) != char.ToLowerInvariant(text[right]))
            return false; // (1)
        left++; right--;
    }
    return true;
}
void Reverse(char[] text)
{
    for (int left = 0, right = text.Length - 1; left < right; left++, right--)
        (text[left], text[right]) = (text[right], text[left]); // (2)
}
string ReverseWords(string text) =>
    string.Join(' ', text.Split(' ', StringSplitOptions.RemoveEmptyEntries).Reverse());
🧾 Code walkthrough
  1. (1) This uses char comparison, not Unicode normalization or full case folding. Restrict the exercise to if international text is outside scope.
  2. (2) Each swap fixes both ends; reversing -16 chars can split surrogate pairs. Real text requires agreed Rune or grapheme semantics.

Complexity: Palindrome and char reversal take O(n) time and O(1) space. ReverseWords takes O(n) time and space and normalizes spaces; tabs are not separators here. Test empty, punctuation-only and single-word inputs.

#10. Longest substring without repetition

Problem: Find the longest substring containing no repeated char; abcabcbb → 3. Idea: Maintain a distinct-character window and a map of last positions. Sorting would destroy the required order.

int LongestUniqueSubstring(string text)
{
    var last = new Dictionary<char, int>();
    int left = 0, best = 0;
    for (int right = 0; right < text.Length; right++)
    {
        if (last.TryGetValue(text[right], out int previous))
            left = Math.Max(left, previous + 1); // (1)
        last[text[right]] = right;
        best = Math.Max(best, right - left + 1); // (2)
    }
    return best;
}
🧾 Code walkthrough
  1. (1) Never move left backwards; in abba, the previous a is outside the current window.
  2. (2) The repaired window is valid, so update its best length.

Complexity: Expected O(n) time, O(min(n, alphabet)) space. Empty input gives zero; repeated identical chars give one. This counts chars; perceived-character requirements need different segmentation and position tracking.

#11. Sorted Two Sum and container with most water

Problems: Return one-based indices summing to target in a sorted array, or empty; find the largest container area between non-negative heights. [2,7,11,15], 9 → [1,2]; [1,8,6,2,5,4,8,3,7] → 49. Idea: Safely discard one endpoint instead of checking every pair.

int[] TwoSumSorted(int[] sorted, int target)
{
    int left = 0, right = sorted.Length - 1;
    while (left < right)
    {
        long sum = (long)sorted[left] + sorted[right];
        if (sum == target) return [left + 1, right + 1];
        if (sum < target) left++; else right--; // (1)
    }
    return [];
}
long MaxArea(int[] heights)
{
    int left = 0, right = heights.Length - 1;
    long best = 0;
    while (left < right)
    {
        long area = (long)Math.Min(heights[left], heights[right]) * (right - left);
        best = Math.Max(best, area);
        if (heights[left] < heights[right]) left++; else right--; // (2)
    }
    return best;
}
🧾 Code walkthrough
  1. (1) If the sum is too small, keeping left while decreasing right cannot help; advance left. Reverse the reasoning for a large sum.
  2. (2) The shorter wall limits height; moving only the taller wall reduces width without raising that limit.

Complexity: Both take O(n) time and O(1) space instead of O(n²). long protects sums and areas for non-negative int heights. Test fewer than two elements, equal heights and absent sum solutions.

#12. Valid parentheses

Problem: Check bracket balance and nesting: ([]{}) is valid, (] is not. Idea: The latest opener must close first, so use a stack. This contract rejects characters other than the six bracket symbols.

bool ValidParentheses(string text)
{
    var stack = new Stack<char>();
    foreach (char c in text)
    {
        if (c is '(' or '[' or '{') { stack.Push(c); continue; }
        char expected = c switch { ')' => '(', ']' => '[', '}' => '{', _ => '\0' };
        if (expected == '\0' || !stack.TryPop(out char open) || open != expected)
            return false; // (1)
    }
    return stack.Count == 0; // (2)
}
🧾 Code walkthrough
  1. (1) Reject missing or mismatched openers; counting opens and closes alone is insufficient.
  2. (2) No opener may remain. Empty input is valid.

Complexity: O(n) time and O(n) worst-case nesting space. Test )(, (( and valid nesting. Checking full source code requires handling strings and comments rather than counting brackets alone.

#13. Warm-ups: FizzBuzz, compression, word count and primes

Problems: Generate FizzBuzz from 1 through non-negative n; compress runs only if shorter; count case-insensitive words using specified separators; test primality. Examples: 15 → FizzBuzz, aaabbc → a3b2c1 but keep the original because length does not improve, 29 → true. Idea: Handle intersecting conditions first, scan runs, and test divisors only through the square root.

IEnumerable<string> FizzBuzz(int n)
{
    for (long i = 1; i <= n; i++)
        yield return (i % 3, i % 5) switch
        {
            (0, 0) => "FizzBuzz", (0, _) => "Fizz", // (1)
            (_, 0) => "Buzz", _ => i.ToString()
        };
}
string Compress(string text)
{
    var buffer = new StringBuilder();
    for (int i = 0; i < text.Length;)
    {
        int end = i + 1;
        while (end < text.Length && text[end] == text[i]) end++;
        buffer.Append(text[i]).Append(end - i); // (2)
        i = end;
    }
    return buffer.Length < text.Length ? buffer.ToString() : text;
}
Dictionary<string, int> WordCount(string text)
{
    var counts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
    char[] separators = [' ', '\t', '\r', '\n', '.', ','];
    foreach (string word in text.Split(separators, StringSplitOptions.RemoveEmptyEntries))
        counts[word] = counts.GetValueOrDefault(word) + 1;
    return counts;
}
bool IsPrime(int value)
{
    if (value < 2) return false;
    for (int divisor = 2; (long)divisor * divisor <= value; divisor++) // (3)
        if (value % divisor == 0) return false;
    return true;
}
🧾 Code walkthrough
  1. (1) Check divisibility by both numbers before individual cases; a long counter avoids wrapping at int.MaxValue.
  2. (2) Read each run once. For a decodable format accepting input digits, define escaping or explicit boundaries.
  3. (3) A factor above the square root has a partner below it; the cast prevents multiplication overflow.

Complexity: FizzBuzz uses O(n) iterator steps and O(1) iterator state excluding produced strings; total digit output depends on number length. Compression is O(n) time and space. WordCount is expected O(L) time and O(L) space with Split, for text length L. Prime testing is O(√value) time and O(1) space. Test 0, 1, 2 and squares such as 49; this tokenizer is not full linguistic analysis.

Linked lists, caches and queues

#14. Reverse a list, detect cycles and find the middle

Problems: Reverse a singly linked list, detect a cycle and return its middle. For even length return the second middle. 1 → 2 → 3 reverses to 3 → 2 → 1. Reverse and Middle require an acyclic list. Idea: Save next before rewiring, or use slow/fast pointers.

public sealed class ListNode(int value, ListNode? next = null)
{
    public int Value = value;
    public ListNode? Next = next;
}
ListNode? ReverseList(ListNode? head)
{
    ListNode? previous = null;
    while (head is not null)
    {
        var next = head.Next; // (1)
        head.Next = previous;
        previous = head;
        head = next;
    }
    return previous;
}
bool HasCycle(ListNode? head)
{
    ListNode? slow = head, fast = head;
    while (fast?.Next is not null)
    {
        slow = slow!.Next;
        fast = fast.Next.Next;
        if (ReferenceEquals(slow, fast)) return true; // (2)
    }
    return false;
}
ListNode? Middle(ListNode? head)
{
    ListNode? slow = head, fast = head;
    while (fast?.Next is not null)
    {
        slow = slow!.Next;
        fast = fast.Next.Next;
    }
    return slow; // (3)
}
🧾 Code walkthrough
  1. (1) next preserves the remaining list before rewiring; previous heads the reversed portion.
  2. (2) In a cycle, fast eventually catches slow; compare node identity, not Value.
  3. (3) When fast reaches the end, slow is in the middle. Empty input returns null.

Complexity: All three take O(n) time and O(1) space; for cycle detection n counts distinct reachable nodes. Recursive reversal requires O(n) stack. Test self-cycles, singletons and odd/even lengths.

#15. Merge two sorted lists

Problem: Merge two ascending lists; [1,3] and [2,4] → [1,2,3,4]. Inputs are acyclic and share no nodes. Idea: Repeatedly select the smaller head, using a dummy node to avoid a first-node special case.

// Uses ListNode from problem 14; reuses and rewires input nodes.
ListNode? MergeTwoLists(ListNode? first, ListNode? second)
{
    var dummy = new ListNode(0);
    var tail = dummy;
    while (first is not null && second is not null)
    {
        if (first.Value <= second.Value)
        {
            tail.Next = first; first = first.Next; // (1)
        }
        else { tail.Next = second; second = second.Next; }
        tail = tail.Next;
    }
    tail.Next = first ?? second; // (2)
    return dummy.Next;
}
🧾 Code walkthrough
  1. (1) Attach one node and advance only its source; choosing first on ties preserves its relative order.
  2. (2) The remaining suffix is already sorted, so attach it directly.

Complexity: O(n + m) time and O(1) auxiliary space; input links are mutated. Preserving inputs requires new nodes and O(n + m) output space. Test two null lists, one empty list and equal values.

#16. An LRU cache

Problem: Implement a positive-capacity cache with expected O(1) get and put, evicting the least recently used item. Idea: A dictionary finds nodes and a doubly linked list tracks recency. At capacity two, put A, put B, get A, put C evicts B.

public sealed class LruCache<TKey, TValue> where TKey : notnull
{
    private readonly int _capacity;
    private readonly Dictionary<TKey, LinkedListNode<(TKey Key, TValue Value)>> _map =
        new();
    private readonly LinkedList<(TKey Key, TValue Value)> _order = new();
    public LruCache(int capacity)
    {
        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity);
        _capacity = capacity;
    }
    public bool TryGet(TKey key, out TValue value)
    {
        if (!_map.TryGetValue(key, out var node))
        {
            value = default!;
            return false;
        }
        _order.Remove(node);
        _order.AddFirst(node); // (1)
        value = node.Value.Value;
        return true;
    }
    public void Put(TKey key, TValue value)
    {
        if (_map.TryGetValue(key, out var existing))
        {
            existing.Value = (key, value);
            _order.Remove(existing);
            _order.AddFirst(existing);
            return;
        }
        if (_map.Count == _capacity)
        {
            var oldest = _order.Last!;
            _map.Remove(oldest.Value.Key);
            _order.RemoveLast(); // (2)
        }
        _map[key] = _order.AddFirst((key, value));
    }
}
🧾 Code walkthrough
  1. (1) The head is most recent; successful gets must also update recency.
  2. (2) Remove from both structures to preserve their shared .

Complexity: Expected O(1) per operation and O(capacity) space with constant-cost key hashing. This is not thread-safe; protect both structures with one locking policy when shared. It has no or byte-based sizing. Use a cache library for application requirements without assuming exact . Test updates, capacity one, misses and invalid capacity.

#17. Queue using two stacks and a Min Stack

Problems: Build behavior using stacks and a stack exposing the current minimum in O(1). Idea: Reverse incoming into outgoing only when outgoing is empty; store the minimum-so-far with each MinStack entry. Pushing 3, 1, 2 gives minimum 1, including after popping 2.

public sealed class StackQueue<T>
{
    private readonly Stack<T> _incoming = new(), _outgoing = new();
    public void Enqueue(T value) => _incoming.Push(value);
    public T Dequeue()
    {
        if (_outgoing.Count == 0)
            while (_incoming.Count > 0) _outgoing.Push(_incoming.Pop()); // (1)
        return _outgoing.Pop();
    }
}
public sealed class MinStack
{
    private readonly Stack<(int Value, int Minimum)> _entries = new();
    public void Push(int value) => _entries.Push((value,
        _entries.Count == 0 ? value : Math.Min(value, _entries.Peek().Minimum))); // (2)
    public int Pop() => _entries.Pop().Value;
    public int GetMin() => _entries.Peek().Minimum;
}
🧾 Code walkthrough
  1. (1) Each item transfers once; outgoing places the oldest at the top.
  2. (2) Each entry remembers the minimum including itself and lower entries; popping restores the earlier state, including duplicate minima.

Complexity: Queue operations are O(1) amortized, but one dequeue may take O(n). MinStack push is O(1) amortized for backing storage; pop and GetMin are O(1). Both use O(n) space and are not thread-safe. Empty removal or minimum access throws InvalidOperationException; offer Try if required.

#18. Kth largest and merging K sorted sequences

Problems: Find the kth largest element counting duplicates, and merge k sorted arrays. [3,2,1,5,6,4], k=2 → 5. Idea: Retain a size-k heap of large values, or keep the next candidate from each sequence in a heap.

int KthLargest(int[] values, int k)
{
    if (k < 1 || k > values.Length) throw new ArgumentOutOfRangeException(nameof(k));
    var heap = new PriorityQueue<int, int>();
    foreach (int value in values)
    {
        heap.Enqueue(value, value);
        if (heap.Count > k) heap.Dequeue(); // (1)
    }
    return heap.Peek();
}
IEnumerable<int> MergeKSorted(int[][] sequences)
{
    var heap = new PriorityQueue<(int Row, int Index), int>();
    for (int row = 0; row < sequences.Length; row++)
        if (sequences[row].Length > 0) heap.Enqueue((row, 0), sequences[row][0]);
    while (heap.TryDequeue(out var item, out int value))
    {
        yield return value; // (2)
        int next = item.Index + 1;
        if (next < sequences[item.Row].Length)
            heap.Enqueue((item.Row, next), sequences[item.Row][next]);
    }
}
🧾 Code walkthrough
  1. (1) Drop the minimum among k+1 candidates; the final root is kth largest.
  2. (2) The smallest current head is globally next; replace it with its successor from the same sequence.

Complexity: kth takes O(n log(k + 1)) time and O(k) space. Merge takes O(k + N log(k + 1)) time and O(k) auxiliary space, excluding materialized output, for N total items. Quickselect offers expected O(n) kth selection but usually mutates input and can degrade to O(n²). Test empty sequences, duplicates and invalid k.

Trees, graphs and dynamic programming

#19. Depth, inversion, level order and BST validation

Problems: Find maximum depth, swap left and right recursively, return levels, and validate a strict without duplicates. Input must be an acyclic tree without shared child nodes. Idea: Recurse over subtrees, use a queue for levels, and propagate ancestor bounds. Root 2 with children 1 and 3 has depth 2 and levels [[2],[1,3]].

public sealed class TreeNode(int value)
{
    public int Value = value;
    public TreeNode? Left, Right;
}
int MaxDepth(TreeNode? node) => node is null ? 0
    : 1 + Math.Max(MaxDepth(node.Left), MaxDepth(node.Right));
TreeNode? Invert(TreeNode? node)
{
    if (node is null) return null;
    (node.Left, node.Right) = (Invert(node.Right), Invert(node.Left)); // (1)
    return node;
}
List<List<int>> LevelOrder(TreeNode? root)
{
    var result = new List<List<int>>();
    if (root is null) return result;
    var queue = new Queue<TreeNode>([root]);
    while (queue.Count > 0)
    {
        var level = new List<int>();
        for (int remaining = queue.Count; remaining > 0; remaining--) // (2)
        {
            var node = queue.Dequeue();
            level.Add(node.Value);
            if (node.Left is not null) queue.Enqueue(node.Left);
            if (node.Right is not null) queue.Enqueue(node.Right);
        }
        result.Add(level);
    }
    return result;
}
bool IsValidBst(TreeNode? node, long minimum = long.MinValue,
    long maximum = long.MaxValue) => node is null
    || (node.Value > minimum && node.Value < maximum // (3)
        && IsValidBst(node.Left, minimum, node.Value)
        && IsValidBst(node.Right, node.Value, maximum));
🧾 Code walkthrough
  1. (1) Invert mutates the input tree rather than copying it.
  2. (2) Freeze this level's node count before adding the next level's children.
  3. (3) Every node must satisfy all ancestor bounds, not just its parent; long bounds admit extreme int values.

Complexity: Each operation takes O(n) time. Recursion uses O(h) stack; level order uses an O(w) queue excluding O(n) output, for maximum width w. Test null, skewed trees and descendants violating a distant ancestor. Use iterative traversal for excessive depth.

#20. Number of islands

Problem: Count horizontally or vertically connected '1' groups in a rectangular '0'/'1' grid; diagonals do not connect. ["110","010","001"] → 2. Idea: Each unvisited land cell starts an island; flood-fill it with to avoid deep recursion.

int NumberOfIslands(char[][] grid)
{
    if (grid.Length == 0) return 0;
    int rows = grid.Length, columns = grid[0].Length, islands = 0;
    (int Dr, int Dc)[] directions = [(1, 0), (-1, 0), (0, 1), (0, -1)];
    var queue = new Queue<(int Row, int Column)>();
    for (int row = 0; row < rows; row++)
        for (int column = 0; column < columns; column++)
        {
            if (grid[row][column] != '1') continue;
            islands++;
            grid[row][column] = '0';
            queue.Enqueue((row, column)); // (1)
            while (queue.TryDequeue(out var cell))
                foreach (var (dr, dc) in directions)
                {
                    int r = cell.Row + dr, c = cell.Column + dc;
                    if (r < 0 || c < 0 || r >= rows || c >= columns) continue;
                    if (grid[r][c] != '1') continue;
                    grid[r][c] = '0'; // (2)
                    queue.Enqueue((r, c));
                }
        }
    return islands;
}
🧾 Code walkthrough
  1. (1) Count once for each newly discovered component.
  2. (2) Mark on enqueue so each cell enters at most once.

Complexity: O(RC) time and O(RC) worst-case queue space. The grid is mutated; use an O(RC) visited structure if mutation is forbidden. Test empty grids, empty rows, all water and all land. Jagged rows require validation or a different contract.

#21. Climbing stairs and House Robber

Problems: Count ways to climb n stairs using steps of one or two, and maximize a sum of non-adjacent non-negative values. The latter is known as House Robber and can model selecting non-adjacent booking slots. n=3 → 3; [2,7,9,3,1] → 12. Idea: Each state depends on the previous two.

long ClimbStairs(int n)
{
    if (n < 0 || n > 91) throw new ArgumentOutOfRangeException(nameof(n));
    long previous = 1, current = 1; // ways(0), ways(1)
    for (int i = 2; i <= n; i++)
        (previous, current) = (current, checked(previous + current)); // (1)
    return current;
}
long MaxNonAdjacent(int[] values)
{
    long previous2 = 0, previous1 = 0;
    foreach (int value in values)
        (previous2, previous1) =
            (previous1, Math.Max(previous1, previous2 + value)); // (2)
    return previous1;
}
🧾 Code walkthrough
  1. (1) The last step was one or two: ways(n)=ways(n-1)+ways(n-2). Zero stairs has one empty route; 91 is the long-result limit.
  2. (2) Skip this value or take it with the optimum before its neighbor. Tuple assignment uses the old states.

Complexity: O(n) time and O(1) space, instead of exponential unmemoized recursion. Test zero/one stairs and empty/singleton arrays. Reconstructing selected positions generally requires O(n) decision or state storage.

#22. Coin change and longest common subsequence

Problems: Find the fewest positive coins forming a non-negative amount, or -1; find the longest common subsequence length, allowing gaps. [1,3,4], 6 → 2; abcde and ace → 3. Idea: Define states, base cases and recurrence before writing loops.

int CoinChange(int[] coins, int amount)
{
    ArgumentOutOfRangeException.ThrowIfNegative(amount);
    if (coins.Any(coin => coin <= 0)) throw new ArgumentException("Positive coins only");
    var best = new int[checked(amount + 1)];
    Array.Fill(best, int.MaxValue);
    best[0] = 0;
    for (int value = 1; value <= amount; value++)
        foreach (int coin in coins)
            if (coin <= value && best[value - coin] != int.MaxValue)
                best[value] = Math.Min(best[value], best[value - coin] + 1); // (1)
    return best[amount] == int.MaxValue ? -1 : best[amount];
}
int Lcs(string first, string second)
{
    var length = new int[first.Length + 1, second.Length + 1];
    for (int i = 1; i <= first.Length; i++)
        for (int j = 1; j <= second.Length; j++)
            length[i, j] = first[i - 1] == second[j - 1]
                ? length[i - 1, j - 1] + 1 // (2)
                : Math.Max(length[i - 1, j], length[i, j - 1]);
    return length[first.Length, second.Length];
}
🧾 Code walkthrough
  1. (1) Choose the last coin and add one to the best remainder; skip unreachable states.
  2. (2) Matching characters extend a subsequence; otherwise skip a character from either prefix and take the better result. Zero row and column represent empty prefixes.

Complexity: Coin change takes O(amount × c) time and O(amount) space; must fit memory, and time is pseudo-polynomial in amount's encoding. takes O(nm) time and space, reducible to O(min(n,m)) space for length alone. Test zero/unreachable amounts and empty strings; greedy is not generally correct for arbitrary coins.

Practical problems and additions

#23. Rate limiting, debounce and retry

Problems: Allow at most N requests per client in a rolling window, process the latest value after a quiet period, and retry a transiently failing read with a bounded policy. Idea: Timestamp queues, cancellation of earlier waits, and bounded backoff with jitter. A limit of two rejects the third request in one window. Debouncing coalesces a burst; it is not rate limiting.

// Also requires System.Collections.Concurrent.
public sealed class SlidingWindowLimiter
{
    private readonly int _limit;
    private readonly TimeSpan _window;
    private readonly TimeProvider _clock;
    private readonly ConcurrentDictionary<string, Queue<long>> _hits = new();
    public SlidingWindowLimiter(int limit, TimeSpan window, TimeProvider clock)
    {
        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(limit);
        if (window <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(window));
        _limit = limit; _window = window; _clock = clock;
    }
    public bool Allow(string client)
    {
        var hits = _hits.GetOrAdd(client, _ => new Queue<long>());
        lock (hits) // (1)
        {
            long now = _clock.GetTimestamp();
            while (hits.Count > 0 && _clock.GetElapsedTime(hits.Peek(), now) >= _window)
                hits.Dequeue();
            if (hits.Count >= _limit) return false;
            hits.Enqueue(now);
            return true;
        }
    }
}

// Single-caller async stream; inputs yield promptly and honor cancellation.
async Task DebounceAsync<T>(IAsyncEnumerable<T> source, TimeSpan quiet,
    Func<T, CancellationToken, Task> apply, CancellationToken ct)
{
    if (quiet <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(quiet));
    using var stop = CancellationTokenSource.CreateLinkedTokenSource(ct);
    await using var iterator = source.GetAsyncEnumerator(stop.Token);
    Task<bool>? next = null;
    try
    {
        next = iterator.MoveNextAsync().AsTask();
        while (await next)
        {
            T latest = iterator.Current;
            next = iterator.MoveNextAsync().AsTask();
            while (true)
            {
                using var timer = CancellationTokenSource.CreateLinkedTokenSource(ct);
                Task delay = Task.Delay(quiet, timer.Token);
                Task winner = await Task.WhenAny(next, delay); // (2)
                if (winner == delay)
                {
                    await delay; // observe cancellation before invoking the handler
                    await apply(latest, ct);
                    break;
                }
                if (!await next)
                {
                    await delay; // finish the existing quiet period, without restarting it
                    await apply(latest, ct); // flush final value after quiet period
                    return;
                }
                timer.Cancel();
                latest = iterator.Current;
                next = iterator.MoveNextAsync().AsTask();
            }
        }
    }
    finally
    {
        stop.Cancel();
        if (next is not null)
        {
            try { await next; }
            catch (OperationCanceledException) when (stop.IsCancellationRequested) { }
        }
    }
}

// The delegate must classify failures: true only for safe transient retries.
async Task<T> RetryReadAsync<T>(Func<CancellationToken, Task<T>> read,
    Func<Exception, bool> isTransient, int attempts, CancellationToken ct)
{
    if (attempts < 1 || attempts > 8)
        throw new ArgumentOutOfRangeException(nameof(attempts));
    for (int attempt = 1; ; attempt++)
    {
        ct.ThrowIfCancellationRequested();
        try { return await read(ct); }
        catch (Exception error) when (error is not OperationCanceledException
            && attempt < attempts && isTransient(error)) // (3)
        {
            int milliseconds = 100 * (1 << (attempt - 1)) + Random.Shared.Next(100);
            await Task.Delay(milliseconds, ct);
        }
    }
}
🧾 Code walkthrough
  1. (1) A per-client lock makes expiration, limit checking and insertion atomic together. Monotonic timestamps suit elapsed-time measurement better than wall-clock time.
  2. (2) A newer value resets the quiet period; only one MoveNext remains outstanding and handlers run serially. Finally cancels and awaits the read before disposal. Slow handlers require an appropriate source buffering policy; this version does not measure original arrival timestamps.
  3. (3) The last attempt propagates failure. The caller classifies transience; cancellation is not retried. Define an overall timeout beyond individual attempt limits.

Complexity: The limiter is expected amortized O(1) per call and O(C × limit) memory; one call can expire O(limit) timestamps. It does not remove inactive clients, so unbounded client IDs require coordinated cleanup that prevents parallel queues for one key. Debounce uses O(n) scheduling and O(1) extra state excluding source/handler buffers. Retry costs O(attempts × read cost), plus delays, with O(1) state.

These are exercises, not complete operational libraries: the limiter is local, and a Redis fixed-window counter is not equivalent to an exact rolling window. Consider rate limiting, a debounce library or pipeline with explicit lifecycle, and Microsoft.Extensions.Http.Resilience for with an -aware policy. Test time boundaries, cancellation, transient-then-success and permanent failure.

#24. Allocate a discount without losing cents

Problem: Allocate an invoice discount proportionally while preserving its exact total. Every amount in this example is in cents: [100,200,300], 100 → [17,33,50]. Idea: Use largest remainders in minor currency units instead of pushing all rounding error into one line, which can create negative shares. Inputs are non-negative integer cents, totals fit long, and discount does not exceed the invoice total.

// Requires System.Numerics for exact integer products.
long[] AllocateCents(long[] lines, long discount)
{
    if (discount < 0 || lines.Any(value => value < 0))
        throw new ArgumentException("Non-negative amounts required");
    long total = 0;
    foreach (long value in lines) total = checked(total + value);
    if (discount > total) throw new ArgumentException("Discount exceeds total");
    var shares = new long[lines.Length];
    if (total == 0) return shares;
    var remainders = new System.Numerics.BigInteger[lines.Length];
    long allocated = 0;
    for (int i = 0; i < lines.Length; i++)
    {
        var product = (System.Numerics.BigInteger)discount * lines[i];
        shares[i] = (long)System.Numerics.BigInteger.DivRem(
            product, total, out remainders[i]); // (1)
        allocated += shares[i];
    }
    int[] order = Enumerable.Range(0, lines.Length)
        .OrderByDescending(i => remainders[i]).ThenBy(i => i).ToArray();
    long remaining = discount - allocated;
    for (int i = 0; i < remaining; i++) shares[order[i]]++; // (2)
    return shares;
}
🧾 Code walkthrough
  1. (1) Take each exact share's floor and retain its remainder; BigInteger avoids overflowing the product of two longs, while each quotient is bounded by discount.
  2. (2) Remaining cents are fewer than the line count; distribute them to largest remainders with deterministic ties.

Complexity: O(n log n) time and O(n) space; products here are bounded by two long inputs. Use validated decimal conversion at monetary input/display boundaries. Cents assume a two-decimal currency, not every currency. Test zero totals, full discounts and many tiny lines; verify exact sum and each share between zero and its line value. policy is a documented business decision.

#25. Group orders and duplicate keys in a large file

Problems: Find the top three customers by paid-order revenue during the last 30 days through now, and detect duplicate SKUs in a file larger than . Idea: Filter, group, and sort; or stream keys with a set. Two paid orders of 10 and 20 for one customer total 30. The file contract is one key per line, not general .

public record Order(int CustomerId, DateTimeOffset CreatedAt,
    string Status, decimal Total);
public record CustomerRevenue(int CustomerId, decimal Revenue);
List<CustomerRevenue> TopCustomers(IEnumerable<Order> orders, DateTimeOffset now) =>
    orders.Where(order => order.Status == "Paid"
        && order.CreatedAt >= now.AddDays(-30) && order.CreatedAt <= now)
    .GroupBy(order => order.CustomerId)
    .Select(group => new CustomerRevenue(group.Key, group.Sum(order => order.Total)))
    .OrderByDescending(customer => customer.Revenue)
    .ThenBy(customer => customer.CustomerId).Take(3).ToList(); // (1)

async IAsyncEnumerable<string> DuplicateSkus(string path,
    [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken ct = default)
{
    var seen = new HashSet<string>(StringComparer.Ordinal);
    using var reader = new StreamReader(path);
    while (await reader.ReadLineAsync(ct) is { } sku)
        if (!seen.Add(sku)) yield return sku; // (2)
}
🧾 Code walkthrough
  1. (1) Ties are deterministic and future orders excluded. Assume one currency and decimal-safe totals. executes in memory; requires translation review.
  2. (2) Every occurrence after the first is emitted, not one output per duplicate key. Matching is case-sensitive; empty lines are valid keys here unless the contract says otherwise.

Complexity: The grouping query takes O(n + u log u) time and O(n + u) space because GroupBy retains elements. Streaming uses expected O(B) processing for B characters but retains every distinct key: memory grows with total distinct-key characters. If that exceeds memory, use external merge sort or stable hash partitioning with consistent equality, then process partitions; skew may require repartitioning. A Bloom filter alone is not exact. General CSV needs quoted-field and embedded-newline parsing, and line-length limits must be defined.

#26. Rotate a matrix

Problem: Rotate an n×n matrix ninety degrees clockwise in place; [[1,2],[3,4]] → [[3,1],[4,2]]. Idea: Transpose, then reverse each row. A non-square matrix changes dimensions and needs a different contract or .

void RotateMatrix(int[][] matrix)
{
    int n = matrix.Length;
    if (matrix.Any(row => row.Length != n))
        throw new ArgumentException("Square matrix required");
    for (int row = 0; row < n; row++)
        for (int column = row + 1; column < n; column++)
            (matrix[row][column], matrix[column][row]) =
                (matrix[column][row], matrix[row][column]); // (1)
    foreach (var row in matrix) Array.Reverse(row); // (2)
}
🧾 Code walkthrough
  1. (1) Swap each above-diagonal pair once; swapping twice would undo the transpose.
  2. (2) Reversing rows makes the rotation clockwise: (r,c) maps to (c,n-1-r).

Complexity: O(n²) time and O(1) extra space. Input is mutated and rows must be distinct arrays rather than aliased references. Test 0×0, 1×1 and 3×3; counterclockwise rotation uses a different reversal arrangement.

#27. Subarray sum equals K

A worked example
  1. For 1, 2, 1 with target 3, the matching contiguous subarrays are the first 1 with 2, and 2 with the last 1: count 2.
  2. A prefix sum totals values up to a position. Subtracting two prefix sums gives the sum between them, so look for prior sums equal to currentSum minus target.
  3. Store frequencies of prior sums and seed sum 0 once for the position before the first element. Count matches before recording the current sum to avoid counting an empty subarray for target zero.

Problem: Count nonempty contiguous subarrays summing to k, allowing negatives and zeros. [1,1,1], 2 → 2; [0,0], 0 → 3. Idea: Since currentPrefix - previousPrefix = k, every earlier occurrence of currentPrefix-k supplies a valid start.

long SubarraySum(int[] values, long k)
{
    var frequency = new Dictionary<long, long> { [0] = 1 }; // (1)
    long prefix = 0, count = 0;
    foreach (int value in values)
    {
        prefix += value;
        // (2) A difference outside long cannot be a stored prefix.
        bool safe = k >= 0 ? prefix >= long.MinValue + k
            : prefix <= long.MaxValue + k;
        if (safe) count += frequency.GetValueOrDefault(prefix - k);
        frequency[prefix] = frequency.GetValueOrDefault(prefix) + 1; // (3)
    }
    return count;
}
🧾 Code walkthrough
  1. (1) One empty prefix counts subarrays starting at index zero.
  2. (2) Prefix sums of an int array fit long, but subtracting a long k may overflow, so guard the difference.
  3. (3) Store after counting to exclude empty subarrays; a frequency map, rather than a set, handles repeated prefixes.

Complexity: Expected O(n) time and O(n) space, versus O(n²) for every start and end. A long count accommodates subarray counts for int-length arrays. The usual sum-based sliding window fails with negatives. Test empty input, negative k, zeros and repeated prefixes.

#28. Course schedule

Problem: Courses are numbered 0 through n-1; each pair [course, prerequisite] requires the second before the first. Can all courses finish? n=2, [[1,0]] → true; adding [0,1] creates a cycle and returns false. Idea: Apply Kahn's topological sort to the directed graph.

bool CanFinish(int n, int[][] prerequisites)
{
    ArgumentOutOfRangeException.ThrowIfNegative(n);
    var graph = Enumerable.Range(0, n).Select(_ => new List<int>()).ToArray();
    var degree = new int[n];
    foreach (var pair in prerequisites)
    {
        int course = pair[0], prerequisite = pair[1];
        graph[prerequisite].Add(course); // (1)
        degree[course]++;
    }
    var ready = new Queue<int>();
    for (int course = 0; course < n; course++)
        if (degree[course] == 0) ready.Enqueue(course);
    int completed = 0;
    while (ready.TryDequeue(out int course))
    {
        completed++;
        foreach (int dependent in graph[course])
            if (--degree[dependent] == 0) ready.Enqueue(dependent); // (2)
    }
    return completed == n; // (3)
}
🧾 Code walkthrough
  1. (1) Edges point from prerequisites to dependents. Pairs must have two valid IDs; duplicate edges are counted consistently during insertion and removal.
  2. (2) Each completed prerequisite reduces the remaining blockers; enqueue a course once its indegree reaches zero.
  3. (3) Unprocessed courses imply a directed cycle, even in a disconnected component.

Complexity: O(V + E) time and space including the graph. With no edges, n=0 returns true. Test self-loops, disconnected courses and duplicate pairs. Store processed vertices if the order itself is requested; valid orders may differ.

Interview questions

Check your reasoning

Why does this choice fit? When would it fail? What changes under higher load or a dependency failure?

29.1How do you approach a coding problem you have never seen before?Junior
Model answer

I restate the problem and clarify constraints and edge cases, then write a couple of examples. I describe a brute-force solution with its complexity, and look for a better approach by matching known patterns such as hashing, two pointers or a sliding window. I explain my reasoning as I code, walk through an example and the edge cases by hand, and finish with the time and space complexity.

29.2Find two numbers in an array that sum to a target. What is the complexity?Junior
Model answer

I iterate once, keeping a dictionary from value to index. For each number I check whether the target minus that number is already in the dictionary; if so I return both indices, otherwise I store the current one. That's O(n) time and O(n) space, instead of O(n squared) for checking every pair. If the array is sorted, two pointers give O(n) time with constant space.

29.3How do you reverse a linked list?Junior
Model answer

Iteratively with three references. previous starts as null; for each node I save its next, point it back to previous, then advance previous and current. When current becomes null, previous is the new head. It runs in O(n) time and O(1) space; a recursive version is shorter but uses O(n) stack.

29.4How do you check whether a string has balanced brackets?Junior
Model answer

With a stack: I push every opening bracket, and on a closing bracket the stack must be non-empty and its top must be the matching opener, which I pop. At the end the stack must be empty. That's O(n) time and space.

29.5Design an LRU cache with O(1) get and put.Mid
Model answer

I combine a dictionary with a doubly linked list ordered by recency. Get moves the node to the front; put inserts or updates at the front and, when capacity is exceeded, evicts the tail node and removes its dictionary entry. List operations are O(1); dictionary operations give expected amortized O(1), rather than a worst-case guarantee. For concurrent use I'd add a lock. MemoryCache with a size limit is a practical alternative, but its eviction policy does not guarantee exact .

29.6How would you find duplicates in a file too large to fit in memory?Senior
Model answer

I stream the file instead of loading it. If the distinct keys fit in memory, a HashSet finds duplicates in one pass. If not, I hash-partition lines into N smaller files by the key's hash, so duplicates always land in the same file, then process each file in memory. Alternatives are an external merge sort followed by comparing neighbours, or a Bloom filter as a first pass.

29.7How would you split a discount across invoice lines without losing cents?Mid
Model answer

I use integer minor currency units and validate non-negative amounts and a discount no greater than the invoice total. I calculate each proportional share exactly, take its floor, then distribute the remaining cents to the largest fractional remainders, breaking ties by line order. Shares sum exactly to the discount, remain non-negative and do not exceed their line amounts. Assigning all rounding error to one line can produce a negative share. I check hand-calculated cases including zero totals, ties and many small lines.

Test yourself

Quiz answers and completed-topic marks are saved in this browser; they do not sync across devices.

Score: 0 / 0

Best approach for 'longest substring without repeating characters'?

A sliding window makes it O(n) instead of O(n³).

Floyd's slow/fast pointer technique detects...

If there's a cycle, the fast pointer catches the slow one.

Which data structures give an O(1) LRU cache?

The dictionary gives lookup; the linked list keeps order and moves nodes.

Kadane's algorithm solves...

It extends the current run or restarts when the sum turns negative.

Top K frequent elements, efficiently?

O(n log k), or O(n) with buckets.

Number of Islands is a classic use of...

Count each new island, then erase it with .

Subarray Sum Equals K is solved in O(n) with...

If prefix − k was seen before, a subarray summing to k ends here.

Course Schedule (can all courses be finished?) is solved with...

A cycle in the prerequisite graph makes completion impossible.