AND (&) = cả hai công tắc đều phải bật → đèn mới sáng
OR (|) = ít nhất một công tắc bật → đèn sáng
XOR (^) = đúng một công tắc bật → “toán tử khác nhau”
NOT (~) = đảo trạng thái
Left shift (<<) = nhân 2 (thêm số 0 vào bên phải)
Right shift (>>) = chia 2 (bỏ bit cuối bên phải)
Tại sao học Bit Manipulation?
Trong thực tế hệ thống, bit manipulation xuất hiện ở:
Space optimization: lưu 64 flags trong một số 64-bit thay vì 64 boolean
Cryptography & hashing: XOR là nền tảng của nhiều cipher
Competitive programming: bitmask DP cho NP-hard problems trên small N
Embedded systems & graphics: pixel blending, shader programming
Networking: subnet masks, IP address operations
FAANG Signal
XOR trick cho “find single number” là câu hỏi phổ biến ở Google, Amazon, Meta. Nếu bạn không biết bit manipulation, bạn sẽ giải O(N) space thay vì O(1) space.
2. The FAANG Standard
Những gì interviewer kỳ vọng ở level L4-L5:
Skill
Description
Basic ops
Nhận ra khi nào dùng &, `
Power-of-2 check
n & (n-1) == 0 — giải thích được tại sao
XOR self-cancellation
a ^ a = 0, a ^ 0 = a
Bitmask enumeration
Enumerate all 2N subsets
Brian Kernighan
Count set bits trong O(number of set bits)
Bit tricks
Get/set/clear/toggle specific bit
Key identities cần thuộc lòng:
a ^ a = 0 (XOR với chính nó = 0)
a ^ 0 = a (XOR với 0 = giữ nguyên)
a & (a-1) (xóa lowest set bit)
a & (-a) (giữ chỉ lowest set bit)
a | (a-1) (set tất cả bit từ lowest xuống)
(a >> k) & 1 (lấy bit thứ k)
a | (1 << k) (set bit thứ k)
a & ~(1 << k) (clear bit thứ k)
a ^ (1 << k) (toggle bit thứ k)
n & (n-1) luôn xóa lowest set bit của n. Vì vậy số lần lặp = số lượng set bits (1s) trong n, không phải tổng số bits.
4. TypeScript Template
4.1 Basic Bit Operations
// ============================================================// BASIC BIT OPERATIONS — Reference Sheet// ============================================================const n = 0b1101; // 13 in binary// --- Kiểm tra bit thứ k (0-indexed từ phải) ---const getBit = (n: number, k: number): number => (n >> k) & 1;// getBit(13, 0) = 1 (bit 0 = 1 trong 1101)// getBit(13, 1) = 0 (bit 1 = 0 trong 1101)// --- Set bit thứ k lên 1 ---const setBit = (n: number, k: number): number => n | (1 << k);// setBit(13, 1) = 1101 | 0010 = 1111 = 15// --- Clear bit thứ k về 0 ---const clearBit = (n: number, k: number): number => n & ~(1 << k);// clearBit(13, 0) = 1101 & 1110 = 1100 = 12// --- Toggle bit thứ k ---const toggleBit = (n: number, k: number): number => n ^ (1 << k);// toggleBit(13, 1) = 1101 ^ 0010 = 1111 = 15// --- Lowest set bit ---const lowestSetBit = (n: number): number => n & (-n);// lowestSetBit(12) = 1100 & 0100 = 0100 = 4// --- Remove lowest set bit (Brian Kernighan trick) ---const removeLowestSetBit = (n: number): number => n & (n - 1);// removeLowestSetBit(12) = 1100 & 1011 = 1000 = 8
4.2 isPowerOfTwo — O(1)
// LeetCode #231// Insight: 2^k có đúng 1 bit = 1. Vì vậy n & (n-1) = 0.// Edge case: n <= 0 không phải power of two.function isPowerOfTwo(n: number): boolean { return n > 0 && (n & (n - 1)) === 0;}// Examples:// isPowerOfTwo(8) = 1000 & 0111 = 0 → true// isPowerOfTwo(6) = 0110 & 0101 = 0100 ≠ 0 → false// isPowerOfTwo(1) = 0001 & 0000 = 0 → true (2^0 = 1)
4.3 Single Number — XOR Trick, O(N) time O(1) space
// LeetCode #136// Insight: a ^ a = 0, a ^ 0 = a// XOR tất cả elements: cặp đôi tự triệt tiêu, số lẻ còn lạifunction singleNumber(nums: number[]): number { return nums.reduce((xor, num) => xor ^ num, 0);}// Example: [4, 1, 2, 1, 2]// 0 ^ 4 ^ 1 ^ 2 ^ 1 ^ 2// = 4 ^ (1^1) ^ (2^2)// = 4 ^ 0 ^ 0 = 4 ✓
4.4 Hamming Weight (Count Set Bits)
// LeetCode #191// Approach 1: Naive O(log N) — loop qua từng bitfunction hammingWeightNaive(n: number): number { let count = 0; while (n !== 0) { count += n & 1; // kiểm tra bit cuối n = n >>> 1; // unsigned right shift (quan trọng trong JS!) } return count;}// Approach 2: Brian Kernighan O(k) — k = số set bits// Luôn nhanh hơn hoặc bằng naive (k <= log N)function hammingWeight(n: number): number { let count = 0; while (n !== 0) { n = n & (n - 1); // xóa lowest set bit count++; } return count;}// Example: n = 13 = 1101// Iteration 1: 1101 & 1100 = 1100 (n=12), count=1// Iteration 2: 1100 & 1011 = 1000 (n=8), count=2// Iteration 3: 1000 & 0111 = 0000 (n=0), count=3 → return 3
4.5 Reverse Bits
// LeetCode #190// Approach: lấy bit cuối của n, đặt vào vị trí tương ứng của resultfunction reverseBits(n: number): number { let result = 0; for (let i = 0; i < 32; i++) { result = (result << 1) | (n & 1); // shift result left, append bit của n n = n >>> 1; // unsigned shift n right } return result >>> 0; // convert to unsigned 32-bit}// Trực quan:// n = 1011 0000 0000 0000 0000 0000 0000 0011// result = 1100 0000 0000 0000 0000 0000 0000 1101
4.6 Subsets via Bitmask Enumeration
// LeetCode #78// Key: với N elements, có 2^N subsets.// Bitmask i từ 0 đến 2^N - 1: bit thứ j = 1 → element j có mặt trong subset.function subsets(nums: number[]): number[][] { const n = nums.length; const result: number[][] = []; for (let mask = 0; mask < (1 << n); mask++) { const subset: number[] = []; for (let j = 0; j < n; j++) { if ((mask >> j) & 1) { // bit j của mask = 1? subset.push(nums[j]); } } result.push(subset); } return result;}// Example: nums = [1, 2, 3]// mask=000 (0): []// mask=001 (1): [1]// mask=010 (2): [2]// mask=011 (3): [1,2]// mask=100 (4): [3]// mask=101 (5): [1,3]// mask=110 (6): [2,3]// mask=111 (7): [1,2,3]// Total: 8 = 2^3 subsets ✓
4.7 Missing Number — XOR Trick
// LeetCode #268// Approach: XOR index 0..n với tất cả elements// Số nào xuất hiện đúng 2 lần (một lần trong index, một lần trong array) sẽ bị triệt tiêu// Số thiếu chỉ xuất hiện 1 lần (trong index) → còn lạifunction missingNumber(nums: number[]): number { let xor = nums.length; // bắt đầu với n (index cuối) for (let i = 0; i < nums.length; i++) { xor ^= i ^ nums[i]; // XOR với cả index lẫn value } return xor;}// Example: nums = [3, 0, 1] (n=3, missing=2)// xor = 3 ^ (0^3) ^ (1^0) ^ (2^1) = 3^3^0^0^1^2^1 = 2 ✓
5. Complexity Deep-dive
Operation
Time
Space
Notes
Single bit op (&, `
, ^`)
O(1)
O(1)
isPowerOfTwo
O(1)
O(1)
Single bit trick
singleNumber
O(N)
O(1)
XOR toàn bộ array
hammingWeight (naive)
O(logN)
O(1)
Loop qua 32 bits
hammingWeight (Kernighan)
O(k)
O(1)
k = số set bits ≤ log N
reverseBits
O(1)
O(1)
Fixed 32 iterations
subsets (bitmask)
O(2N⋅N)
O(2N⋅N)
2N masks, N bits each
missingNumber
O(N)
O(1)
XOR trick
Why Kernighan is Faster
Nếu n = 1000 0000 0000 0000 (chỉ 1 set bit), naive cần 16 iterations (loop 16 bits), Kernighan chỉ cần 1 iteration. Với số thưa bit, Kernighan thắng đáng kể.
Bitmask DP: Với N ≤ 20, bitmask DP chạy trong O(2N⋅N) — chậm nhưng chấp nhận được. Với N > 20, không feasible.
6. Edge Cases & Pitfalls
JavaScript >>> vs >>
Trong JavaScript, tất cả bitwise operators làm việc với 32-bit signed integers.
>> = arithmetic right shift (giữ sign bit — vấn đề với số âm!)
>>> = logical (unsigned) right shift (luôn thêm 0 vào trái)