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.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
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.
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 fort— 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 -1int 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
| Problem | Time | Space |
|---|---|---|
| Valid anagram | O(n) | O(1) — fixed 26 |
| Group anagrams | O(n·k log k) | O(n·k) |
| Ransom note | O(m + n) | O(1) |
| First unique | O(n) | O(1) |
Premium Content
Unlock Hashing & Anagrams and all premium lessons with a subscription.
From ₹199.99/year — See plans