// pattern debugger≡ menu

stack>core patterns / hashmap

// HashMap & Frequency Counting

Trade O(n) space for O(1) lookups: value→index, value→count, prefixSum→count, and HashSet membership — the "key is what I need" mindset.

core idea

A hashmap turns “have I seen this before?” and “where did I see it?” into O(1) operations. You trade O(n) space for the lookup, and in exchange you get to process unsorted data in one pass — no sort step, no lost original indices. The whole skill is picking the right key. Four shapes cover almost every hashmap problem you’ll meet:

shape key → value answers
value → index Dictionary<int, int> “where did I see the number I need?”
value → count Dictionary<T, int> “how many times has this appeared?”
prefixSum → count Dictionary<int, int> “how many earlier running totals make a valid range?”
membership HashSet<T> “have I seen this at all?” (no value needed, just yes/no)
i
2
0
7
1
11
2
15
3
value -> index: the dictionary key answers 'who is waiting for this number?'

when to reach for it

  • The brute force is a nested loop checking every pair or every prior element — and the input is not sorted, or sorting would destroy something you need (original indices, order).
  • The question is phrased as “have I seen X before” or “how many times does X occur” — HashSet<T> membership answers the first in one line; that one line, on its own, is the entire trick behind Contains Duplicate (LC 217).
  • You need a running total (sum, count, balance) and the question is “how many sub-ranges hit a target” — that’s prefixSum → count.
  • Grouping or bucketing items by some derived property (“same letters”, “same remainder”) — the derived property becomes the key.

key = what I need

Before reaching for a dictionary, ask: “what do I wish I already knew about the elements I’ve passed?” Whatever that is becomes the key. Two Sum: “is my complement already here?” → key is the value. Subarray Sum: “has this running total shown up before?” → key is the prefix sum.

universal templates

value → index — the lookup that turns O(n²) pair-checking into O(n):

public int[] ValueToIndex(int[] nums, int target)
{
    var seen = new Dictionary<int, int>();      // value -> index already seen

    for (int i = 0; i < nums.Length; i++)
    {
        int need = target - nums[i];            // "key = what I need"
        if (seen.TryGetValue(need, out int j)) return [j, i];

        seen[nums[i]] = i;                       // record AFTER checking — can't pair with itself
    }
    return [];
}

value → count — the frequency table underneath anagrams, majority elements, and top-K:

public Dictionary<int, int> Frequencies(int[] nums)
{
    var counts = new Dictionary<int, int>();
    foreach (int n in nums)
        counts[n] = counts.GetValueOrDefault(n) + 1;   // 0 the first time, then increments
    return counts;
}

prefixSum → count — every “how many subarrays sum to k” question is this loop:

public int PrefixSumToCount(int[] nums, int k)
{
    var prefixCount = new Dictionary<int, int> { [0] = 1 };  // empty prefix seen once
    int sum = 0, count = 0;

    foreach (int n in nums)
    {
        sum += n;
        count += prefixCount.GetValueOrDefault(sum - k);      // earlier prefixes that complete a sum-k run
        prefixCount[sum] = prefixCount.GetValueOrDefault(sum) + 1;
    }
    return count;
}

membership — the whole idea in three lines:

public bool HasDuplicate(int[] nums)
{
    var seen = new HashSet<int>();
    foreach (int n in nums)
    {
        if (!seen.Add(n)) return true;   // Add returns false when the value was already present
    }
    return false;
}

Every problem below is one of these four skeletons with a different key.

problems

  1. 01Two Sum (Unsorted)easyLC #1▶ interactive

    The dictionary key answers: "who is waiting for this number?"

  2. 02Valid AnagrameasyLC #242

    value→count frequency table; int[26] as the specialized dictionary.

  3. 03Group AnagramsmediumLC #49

    Canonical form as the dictionary key — group by signature.

  4. HashSet membership + only start counting at sequence starts.

  5. 05Subarray Sum Equals KmediumLC #560

    prefixSum→count: the hashmap remembers every running total seen so far.

  6. 06Insert Delete GetRandom O(1)mediumLC #380stretch

    The dict+list swap-remove idiom — a design question in disguise.

cheat sheet — hashmap

recognize it

  • "have I seen this before?" / "how many times has X occurred?" -> HashSet<T> membership or Dictionary<T,int> frequency count
  • unsorted array + pair/sum question, or sorting would destroy something you need (original indices) -> value->index dictionary
  • "how many subarrays / sub-ranges sum to k" -> prefixSum->count dictionary
  • grouping or bucketing items by a derived property ("same letters", "same remainder") -> canonical-key grouping

key tricks

  • "key = what I need": ask what you wish you already knew about the elements you've passed - that's the dictionary key
  • seed prefixCount[0] = 1 before scanning subarray-sum problems - covers a run that starts at index 0
  • int[26] beats Dictionary<char, int> when the key space is small and fixed (lowercase letters, digits)
  • check the complement/target BEFORE inserting the current element into the map, so nothing ever pairs with itself
  • swap-with-last (values[idx] = values[^1], then drop the tail) turns O(n) arbitrary removal into O(1)

common bugs

  • inserting into the dictionary before checking for the complement - lets an element pair with itself
  • forgetting the prefixCount[0] = 1 seed - silently undercounts subarrays that start at index 0
  • sorting an array to "make it easier" when the problem needs original indices - sorting is exactly what destroys them
  • dict[key] throws KeyNotFoundException on a miss - reach for TryGetValue/GetValueOrDefault instead of guarding manually

// connections