Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Hashing & Anagrams
DSA

Hashing & Anagrams

Use hashing and character frequencies to solve anagram, grouping, and string matching problems.

String hashing compares strings by character frequency instead of order.

Focus on recognizing:

“Anagram” / “how many of each char” → count array (a–z) or HashMap


Pattern 1: Valid Anagram

Watch "listen" fill the counts and "silent" drain them back to zero. Press to animate.

Valid Anagram

Two strings are anagrams if they use the exact same letters the same number of times. Count the characters of one string, then verify the other consumes every count to exactly zero.

s1 = "listen" (top row), s2 = "silent" (bottom row). Build a frequency count from s1, then walk s2 and consume one count per character. If every count reaches exactly zero with no negatives, they are anagrams. The highlighted cell on each step is the character being processed in s2.

GRID VISUALIZER
Steps
l
i
s
t
e
n
s
i
l
e
n
t
Press ▶ to animate, or step through manually.
Variables
keys: ← → space F
Pseudocode

                        1
                        if len(s1) != len(s2): return false
                      
                        2
                        for ch in s1: count[ch]++
                      
                        3
                        for ch in s2:
                      
                        4
                          if count[ch] == 0: return false
                      
                        5
                          count[ch]--
                      
                        6
                        return true
                      
public boolean isAnagram(String s, String t) {
    if (s.length() != t.length()) return false;

    int[] count = new int[26];

    for (int i = 0; i < s.length(); i++) {
        count[s.charAt(i) - 'a']++;
        count[t.charAt(i) - 'a']--;
    }

    for (int c : count) {
        if (c != 0) return false;
    }

    return true;
}
def is_anagram(s, t):
    if len(s) != len(t):
        return False

    count = [0] * 26

    for a, b in zip(s, t):
        count[ord(a) - 97] += 1
        count[ord(b) - 97] -= 1

    return all(c == 0 for c in count)
bool isAnagram(string s, string t) {
    if (s.size() != t.size()) return false;

    int count[26] = {0};

    for (int i = 0; i < (int)s.size(); i++) {
        count[s[i] - 'a']++;
        count[t[i] - 'a']--;
    }

    for (int c : count)
        if (c != 0) return false;

    return true;
}
function isAnagram(s, t) {
  if (s.length !== t.length) return false;

  const count = new Array(26).fill(0);

  for (let i = 0; i < s.length; i++) {
    count[s.charCodeAt(i) - 97]++;
    count[t.charCodeAt(i) - 97]--;
  }

  return count.every((c) => c === 0);
}

One array, increment for s, decrement for t — anagram iff every cell ends at zero.


Increment then decrement in one pass — any nonzero cell means “not an anagram”.


Pattern 2: Group Anagrams

Sorted string (or frequency signature) as the map key:

public List<List<String>> groupAnagrams(String[] strs) {
    Map<String, List<String>> groups = new HashMap<>();

    for (String s : strs) {
        char[] chars = s.toCharArray();
        Arrays.sort(chars);
        String key = new String(chars);

        groups.computeIfAbsent(key, k -> new ArrayList<>())
              .add(s);
    }

    return new ArrayList<>(groups.values());
}
from collections import defaultdict

def group_anagrams(strs):
    groups = defaultdict(list)

    for s in strs:
        key = "".join(sorted(s))
        groups[key].append(s)

    return list(groups.values())
vector<vector<string>> groupAnagrams(vector<string>& strs) {
    unordered_map<string, vector<string>> groups;

    for (string& s : strs) {
        string key = s;
        sort(key.begin(), key.end());
        groups[key].push_back(s);
    }

    vector<vector<string>> result;
    for (auto& [_, g] : groups) result.push_back(g);
    return result;
}
function groupAnagrams(strs) {
  const groups = new Map();

  for (const s of strs) {
    const key = [...s].sort().join("");
    if (!groups.has(key)) groups.set(key, []);
    groups.get(key).push(s);
  }

  return [...groups.values()];
}

O(n·k log k) with sorting; a 26-count tuple key makes it O(n·k).


Pattern 3: Ransom Note

Can ransom be built from letters of magazine?

public boolean canConstruct(String ransom, String magazine) {
    int[] count = new int[26];

    for (char c : magazine.toCharArray())
        count[c - 'a']++;

    for (char c : ransom.toCharArray())
        if (--count[c - 'a'] < 0)
            return false;

    return true;
}
from collections import Counter

def can_construct(ransom, magazine):
    need = Counter(ransom)
    have = Counter(magazine)

    return all(have[ch] >= n for ch, n in need.items())
bool canConstruct(string ransom, string magazine) {
    int count[26] = {0};

    for (char c : magazine) count[c - 'a']++;

    for (char c : ransom)
        if (--count[c - 'a'] < 0)
            return false;

    return true;
}
function canConstruct(ransom, magazine) {
  const count = new Array(26).fill(0);

  for (const c of magazine) count[c.charCodeAt(0) - 97]++;

  for (const c of ransom)
    if (--count[c.charCodeAt(0) - 97] < 0) return false;

  return true;
}

Pattern 4: First Unique Character

public int firstUniqChar(String s) {
    int[] count = new int[26];

    for (char c : s.toCharArray())
        count[c - 'a']++;

    for (int i = 0; i < s.length(); i++)
        if (count[s.charAt(i) - 'a'] == 1)
            return i;

    return -1;
}
from collections import Counter

def first_uniq_char(s):
    count = Counter(s)

    for i, ch in enumerate(s):
        if count[ch] == 1:
            return i

    return -1
int firstUniqChar(string s) {
    int count[26] = {0};

    for (char c : s) count[c - 'a']++;

    for (int i = 0; i < (int)s.size(); i++)
        if (count[s[i] - 'a'] == 1)
            return i;

    return -1;
}
function firstUniqChar(s) {
  const count = new Array(26).fill(0);

  for (const c of s) count[c.charCodeAt(0) - 97]++;

  for (let i = 0; i < s.length; i++)
    if (count[s.charCodeAt(i) - 97] === 1) return i;

  return -1;
}

Two passes: count everything, then find the first index whose count is exactly 1.


Common Mistakes

Using a HashMap for known a–z input.

int[26] is faster and simpler — reserve maps for unknown alphabets.


Skipping the length check.

Different lengths can never be anagrams — exit before counting.


Comparing anagram strings with == or sorting inside the loop.

Count once per string; sort only when grouping.


Complexity

ProblemTimeSpace
Valid anagramO(n)O(1) — fixed 26
Group anagramsO(n·k log k)O(n·k)
Ransom noteO(m + n)O(1)
First uniqueO(n)O(1)

My Private Notes

Notes are auto-saved locally to this device.