A bitmask packs the character set of a string into one integer — bit c - 'a' says whether c is present.
Focus on recognizing:
“Which characters exist” (not how many) + small alphabet → one int instead of a Set
Core Operations
add ch → mask |= 1 << (ch - 'a')
has ch → mask & (1 << (ch - 'a')) != 0
common(A,B) → maskA & maskB
union(A,B) → maskA | maskB
subset → (sub & super) == sub
distinct → popcount(mask)
Pattern 1: All Characters Unique
The mask building over "LEETCODE" — bit 4 ('e') is already set when the second E arrives. 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.
Unique Characters (Bitmask)
Test if a string has all distinct characters using a 26-bit integer mask: one bit per letter. If a character's bit is already set when you reach it, the string repeats.
Input: L E E T C O D E. Walk left to right; for each letter compute its bit (ch - 'a') and check the mask. 'L' and the first 'E' set fresh bits. The second 'E' finds bit 4 already on → duplicate detected and we bail immediately. One integer replaces a HashSet and runs in O(n).
1
mask = 0
2
for ch in s:
3
bit = 1 << (ch - 'a')
4
if mask & bit: return false // already seen
5
mask |= bit
6
return true
Set-bit-before-check detects duplicates in one pass:
public boolean hasUniqueChars(String s) {
int mask = 0;
for (char c : s.toCharArray()) {
int bit = 1 << (c - 'a');
if ((mask & bit) != 0)
return false; // already seen
mask |= bit;
}
return true;
}def has_unique_chars(s):
mask = 0
for ch in s:
bit = 1 << (ord(ch) - 97)
if mask & bit:
return False # already seen
mask |= bit
return Truebool hasUniqueChars(const string& s) {
int mask = 0;
for (char c : s) {
int bit = 1 << (c - 'a');
if (mask & bit)
return false; // already seen
mask |= bit;
}
return true;
}function hasUniqueChars(s) {
let mask = 0;
for (const c of s) {
const bit = 1 << (c.charCodeAt(0) - 97);
if (mask & bit) return false; // already seen
mask |= bit;
}
return true;
}Check-then-set with one integer replaces an entire HashSet.
Pattern 2: Common Characters Between Two Strings
private int maskOf(String s) {
int m = 0;
for (char c : s.toCharArray())
m |= 1 << (c - 'a');
return m;
}
public List<Integer> commonChars(String[] words) {
// e.g. words containing at least one shared letter:
List<Integer> result = new ArrayList<>();
for (int i = 0; i < words.length; i++)
for (int j = i + 1; j < words.length; j++)
if ((maskOf(words[i]) & maskOf(words[j])) != 0) {
result.add(i);
result.add(j);
}
return result;
}def mask_of(s):
m = 0
for ch in s:
m |= 1 << (ord(ch) - 97)
return m
def pairs_with_common_char(words):
masks = [mask_of(w) for w in words]
return [
(i, j)
for i in range(len(words))
for j in range(i + 1, len(words))
if masks[i] & masks[j] # any shared bit
]int maskOf(const string& s) {
int m = 0;
for (char c : s)
m |= 1 << (c - 'a');
return m;
}
vector<pair<int,int>> pairsWithCommonChar(vector<string>& words) {
vector<int> masks;
for (auto& w : words) masks.push_back(maskOf(w));
vector<pair<int,int>> result;
for (int i = 0; i < (int)masks.size(); i++)
for (int j = i + 1; j < (int)masks.size(); j++)
if (masks[i] & masks[j]) // any shared bit
result.push_back({i, j});
return result;
}const maskOf = (s) => {
let m = 0;
for (const c of s) m |= 1 << (c.charCodeAt(0) - 97);
return m;
};
function pairsWithCommonChar(words) {
const masks = words.map(maskOf);
const result = [];
for (let i = 0; i < masks.length; i++)
for (let j = i + 1; j < masks.length; j++)
if (masks[i] & masks[j])
// any shared bit
result.push([i, j]);
return result;
}AND answers “any character in common” in one instruction — no nested loops over letters.
Pattern 3: Subset Check & Distinct Count
// Is every char of t inside s?
public boolean charsSubset(String t, String s) {
return (maskOf(t) & ~maskOf(s)) == 0;
}
// How many distinct chars?
public int distinctCount(String s) {
return Integer.bitCount(maskOf(s));
}def chars_subset(t, s):
# Is every char of t inside s?
return mask_of(t) & ~mask_of(s) == 0
def distinct_count(s):
return bin(mask_of(s)).count("1")bool charsSubset(const string& t, const string& s) {
// Is every char of t inside s?
return (maskOf(t) & ~maskOf(s)) == 0;
}
int distinctCount(const string& s) {
return __builtin_popcount(maskOf(s));
}function charsSubset(t, s) {
// Is every char of t inside s?
return (
(maskOf(t) & ~maskOf(s)) % 4294967296 === 0 ||
(maskOf(t) & ~maskOf(s)) === 0
);
}
function distinctCount(s) {
let m = maskOf(s),
count = 0;
while (m) {
m &= m - 1; // clear lowest set bit
count++;
}
return count;
}
(sub & ~super) == 0is the cleanest subset test;popcountcounts distinct letters.
Common Mistakes
Using bitmask when frequency matters.
A mask stores presence only — "aab" and "ab" have identical masks.
Alphabets larger than the int.
26 lowercase fits int; mixed case needs 52+ bits → use long or two ints.
Forgetting operator precedence.
mask & bit != 0 parses as mask & (bit != 0) in some languages — always parenthesize.
Complexity
| Operation | Time | Space |
|---|---|---|
| Build mask | O(n) | O(1) |
| Compare two strings | O(1) after build | O(1) |
| Uniqueness | O(n) | O(1) |
Premium Content
Unlock String Bitmask and all premium lessons with a subscription.
From ₹199.99/year — See plans