DSA DAY ONE
DSA DAY ONE
PART I — FOUNDATIONS
Chapter 1: C++ Essentials for DSA
Chapter 2: C++ STL Deep Dive
Chapter 3: Complexity Analysis
PART II — LINEAR DATA STRUCTURES
Chapter 4: Arrays & Strings
Chapter 5: Two Pointers & Sliding Window
Chapter 6: Hashing
Chapter 7: Linked Lists
Chapter 8: Stacks
Chapter 9: Queues & Deques
PART III — TREES
Chapter 10: Binary Trees
Chapter 11: Binary Search Trees (BST)
Chapter 12: Heaps & Priority Queues
Chapter 13: Tries (Prefix Trees)
Chapter 14: Segment Tree & Fenwick Tree
PART IV — ALGORITHMS
Chapter 15: Sorting Algorithms
Chapter 16: Binary Search
Chapter 17: Recursion & Backtracking
Chapter 18: Graphs — Fundamentals & Traversals
Chapter 19: Graph Algorithms — Shortest Paths, MST & Union-Find
Chapter 20: Dynamic Programming
Chapter 21: Greedy Algorithms
Chapter 22: Divide & Conquer
PART V — COMPETITIVE & MATH
Chapter 23: Bit Manipulation
Binary Representation & Two's ComplementBitwise Operators — Complete ReferenceEssential Bit Tricks Cheat SheetXOR Magic — Properties & ApplicationsCounting Set BitsBitmask for Subset EnumerationPower of 2 TricksBit DP PatternsSample ProblemsLeetCode Problem Set
Chapter 24: Math & Number Theory
PART VI — PATTERNS
Chapter 25: Pattern Recognition Mastery
PART VII — SYSTEM DESIGN
Chapter 26: System Design for FAANG Interviews
PART VIII — INTERVIEW STRATEGY
Chapter 27: Cracking FAANG — Strategy & Communication
23

Bit Manipulation


Chapter 23 ·  Part 5: COMPETITIVE & MATH ·  Est. 24 min read

Chapter Goal: Develop fluency with bitwise operations — the low-level toolkit that turns O(n) solutions into O(1), counts efficiently, and enables elegant subset enumeration. Bit manipulation problems appear across all difficulty levels in FAANG interviews and are beloved for their compactness. This chapter builds from binary representation through XOR magic, popcount, and bitmask DP.


23.1 Binary Representation & Two's Complement

Unsigned Binary

Decimal 13 → Binary 00001101
  Bit position: 7 6 5 4 3 2 1 0
  Bit value:    0 0 0 0 1 1 0 1
  = 1×2³ + 1×2² + 0×2¹ + 1×2⁰ = 8 + 4 + 1 = 13

Two's Complement (Signed Integers)

Computers represent negative numbers using two's complement. Negating a number: flip all bits, then add 1.

Positive 5:  00000101
Flip bits:   11111010
Add 1:       11111011  ← This is -5 in two's complement

Key values (32-bit int):
  INT_MAX  =  2,147,483,647 = 0x7FFFFFFF (0 followed by 31 ones)
  INT_MIN  = -2,147,483,648 = 0x80000000 (1 followed by 31 zeros)
  -1       =               = 0xFFFFFFFF (all ones)
  0        =               = 0x00000000 (all zeros)

Overflow: INT_MAX + 1 = INT_MIN (wraps around)

Key Two's Complement Identities

// -n = ~n + 1  (negation via bit flip + 1)
// ~n = -n - 1  (bitwise NOT of n)
// -1 = ~0      (all bits set)
 
int n = 13;
cout << (~n + 1);   // -13  ← negate using complement
cout << ~(-1);      // 0    ← NOT of all-ones = all-zeros
cout << (n & -n);   // 1    ← isolate lowest set bit: 1101 & 0011 = 0001
 
// Signed right shift (>>) fills with sign bit (arithmetic shift)
int neg = -8;       // 11111000
cout << (neg >> 1); // -4 = 11111100 (fills with 1 because negative)
 
// Use unsigned right shift via cast when needed:
unsigned int u = (unsigned int)neg >> 1;  // Fills with 0

Bit Width Reference

Type Width Range
int8_t 8-bit −128 to 127
int / int32_t 32-bit −2³¹ to 2³¹−1
long long / int64_t 64-bit −2⁶³ to 2⁶³−1
unsigned int 32-bit 0 to 2³²−1
size_t 64-bit (usually) 0 to 2⁶⁴−1

23.2 Bitwise Operators — Complete Reference

int a = 0b1010;   // 10 in decimal
int b = 0b1100;   // 12 in decimal
 
// AND: both bits must be 1
int andResult = a & b;    // 0b1000 = 8
// Use for: masking, clearing bits, checking bit state
 
// OR: at least one bit must be 1
int orResult  = a | b;    // 0b1110 = 14
// Use for: setting bits, combining flags
 
// XOR: bits must differ (exclusive or)
int xorResult = a ^ b;    // 0b0110 = 6
// Use for: toggling bits, detecting differences, encryption
 
// NOT: flip all bits (unary)
int notResult = ~a;        // 0b...11110101 = -11 (two's complement)
// Use for: creating masks, complement operations
 
// Left Shift: multiply by 2^k (fills with 0 on right)
int lshift = a << 2;      // 0b101000 = 40 = 10 × 4
// Use for: fast multiplication by powers of 2, building masks
 
// Right Shift: divide by 2^k (arithmetic: fills with sign bit)
int rshift = a >> 1;      // 0b0101 = 5 = 10 / 2
// Use for: fast division by powers of 2, extracting upper bits

Operator Precedence (Lowest to Highest)

Lowest:  |   (OR)
         ^   (XOR)
         &   (AND)
         << >> (shifts)
         + -  (add/sub)
         * /  (mul/div)
         ~    (NOT, unary)
Highest: ()  (parentheses)

Warning: ALWAYS USE PARENTHESES in bit expressions to be safe:
   (n >> k) & 1    ← correct
   n >> k & 1      ← parsed as n >> (k & 1) ← WRONG!

Common Shift Idioms

// Build a mask with bit k set:
int mask = 1 << k;           // 32-bit: only works for k < 32
long long lmask = 1LL << k;  // 64-bit: use for k up to 63
 
// Check if k-th bit is set:
bool isSet = (n >> k) & 1;   // Shift k out, check the last bit
bool isSet2 = (n & (1 << k)) != 0;  // Also valid
 
// Signed overflow warning:
// 1 << 31 is UNDEFINED BEHAVIOR in C++ for signed int!
// Use 1u << 31 or 1LL << 31 instead

23.3 Essential Bit Tricks Cheat Sheet

// ─── SINGLE BIT OPERATIONS ──────────────────────────────
int n = 0b10110100;
int k = 2;           // Target bit position (0-indexed from right)
 
// CHECK if bit k is set → returns 0 or 1
int bit = (n >> k) & 1;                    // 1 if set, 0 if not
 
// SET bit k to 1 (regardless of current value)
int set = n | (1 << k);                    // 0b10110100 | 0b00000100 = 0b10110100
 
// CLEAR bit k to 0
int clear = n & ~(1 << k);                 // n & 0b...11111011
 
// TOGGLE bit k (0→1, 1→0)
int toggle = n ^ (1 << k);                 // XOR flips the bit
 
// ─── LOWEST SET BIT (LSB) ───────────────────────────────
int lsb = n & (-n);                        // Isolate lowest set bit
// e.g., 0b10110100 → 0b00000100 (bit 2)
// Why: -n flips all bits and adds 1, canceling all but LSB
 
int clearLSB = n & (n - 1);               // Clear the lowest set bit
// e.g., 0b10110100 → 0b10110000 (clears bit 2)
 
// ─── HIGHEST SET BIT (MSB) ──────────────────────────────
int pos = 31 - __builtin_clz(n);          // Position of MSB (n > 0)
// __builtin_clz: count leading zeros (undefined for n=0)
 
// ─── RANGE OPERATIONS ───────────────────────────────────
// Create mask of k ones: (1 << k) - 1 = 0b00...0111...1 (k ones)
int mask = (1 << k) - 1;                  // k=3 → 0b111
 
// Extract bits [lo, hi]:
int bits = (n >> lo) & ((1 << (hi-lo+1)) - 1);
 
// ─── BUILT-IN FUNCTIONS ─────────────────────────────────
__builtin_popcount(n)     // Count set bits (32-bit)
__builtin_popcountll(n)   // Count set bits (64-bit)
__builtin_ctz(n)          // Count trailing zeros (position of LSB)
__builtin_clz(n)          // Count leading zeros (0-indexed from MSB)
__builtin_parity(n)       // Parity: 1 if odd number of set bits, 0 if even

Bit Trick Application Table

Operation Expression Example (n=0b1010)
Check bit k (n>>k)&1 bit 1 = 1
Set bit k n|(1<<k) set bit 2 → 0b1110
Clear bit k n&~(1<<k) clear bit 1 → 0b1000
Toggle bit k n^(1<<k) toggle bit 0 → 0b1011
Isolate LSB n&(-n) → 0b0010
Clear LSB n&(n-1) → 0b1000
Is power of 2 n>0&&!(n&(n-1)) false (1010≠power of 2)
Count set bits __builtin_popcount(n) 2
Trailing zeros __builtin_ctz(n) 1

23.4 XOR Magic — Properties & Applications

XOR Properties

a ^ 0 = a          (identity: XOR with 0 returns same value)
a ^ a = 0          (self-inverse: XOR with itself returns 0)
a ^ b = b ^ a      (commutative)
(a ^ b) ^ c = a ^ (b ^ c)  (associative)

Derived:
a ^ b ^ a = b      (a cancels out: handy for "find the survivor")
a ^ (~a) = -1      (n XOR its complement = all 1s)

XOR Swap (No Temp Variable)

// Swap a and b without a temporary variable
// Note: FAILS if a and b are the same variable/location!
void xorSwap(int& a, int& b) {
    if (&a == &b) return;  // Guard: same address would zero both out
    a ^= b;
    b ^= a;
    a ^= b;
}
// a=5, b=3:
// a = 5^3 = 6 (0110)
// b = 3^6 = 5 (original a)
// a = 6^5 = 3 (original b)

Find the Single Number (XOR all elements)

// All numbers appear twice EXCEPT one. XOR all → pairs cancel → survivor remains.
int singleNumber(vector<int>& nums) {
    int result = 0;
    for (int x : nums) result ^= x;
    return result;
}
// a^a = 0, 0^b = b → all pairs cancel, single number survives
// Time: O(n), Space: O(1)

Find Missing Number (LC 268)

int missingNumber(vector<int>& nums) {
    int n = nums.size(), result = n;  // Start XOR with n
    for (int i = 0; i < n; i++) result ^= i ^ nums[i];
    return result;
    // XOR indices [0..n] with elements: every pair cancels, missing survives
}
// Alternative: result = n*(n+1)/2 - sum(nums)

Find Two Non-Repeating Numbers (LC 260)

// All numbers appear twice EXCEPT two: a and b.
vector<int> singleNumber(vector<int>& nums) {
    int xorAll = 0;
    for (int x : nums) xorAll ^= x;   // xorAll = a ^ b
 
    // Find any set bit in a^b — it differs between a and b
    // Use lowest set bit to split nums into two groups
    int diffBit = xorAll & (-xorAll);  // Isolate lowest differing bit
 
    int groupA = 0, groupB = 0;
    for (int x : nums) {
        if (x & diffBit) groupA ^= x;  // Group 1: bit is set
        else             groupB ^= x;  // Group 2: bit is not set
    }
    return {groupA, groupB};
    // Each group contains one of {a, b} plus pairs (which cancel)
}
// Time: O(n), Space: O(1)

XOR for Detecting Changes

// Find the single changed bit between two values:
int changed = a ^ b;          // Set bits = positions where a and b differ
// Count differences:
int numDiff = __builtin_popcount(a ^ b);   // Hamming distance
 
// XOR as "difference detector" for arrays:
// If XOR of subarray [l..r] == 0, all elements cancel (each appears even times)

23.5 Counting Set Bits

Method 1 — Naive Loop: O(log n)

int countBits_naive(int n) {
    int count = 0;
    while (n) { count += n & 1; n >>= 1; }
    return count;
}

Method 2 — Brian Kernighan: O(set bits)

Each iteration clears the lowest set bit. Fast when few bits are set.

int countBits_BK(int n) {
    int count = 0;
    while (n) { n &= (n-1); count++; }   // n&(n-1) clears lowest set bit
    return count;
}
// For n = 0b10110100: 3 iterations (3 set bits) vs naive's 8 iterations

Method 3 — __builtin_popcount: O(1)

int count = __builtin_popcount(n);      // 32-bit integer
int count64 = __builtin_popcountll(n); // 64-bit integer
// Compiles to single hardware instruction (POPCNT) on modern CPUs

Counting Total Set Bits from 1 to n

// Count total set bits in ALL numbers from 0 to n
long long totalSetBits(long long n) {
    long long count = 0;
    for (long long bit = 1; bit <= n; bit <<= 1) {
        long long cycle = bit << 1;          // Full cycle length
        count += (n / cycle) * bit;           // Complete cycles
        count += max(0LL, (n % cycle) - bit + 1);  // Partial cycle
    }
    return count;
}
// Time: O(log n), Space: O(1)

Counting Bits DP (LC 338)

// For each i: dp[i] = dp[i>>1] + (i & 1)
// i>>1 removes the last bit; (i&1) accounts for that last bit
vector<int> countBits(int n) {
    vector<int> dp(n+1, 0);
    for (int i = 1; i <= n; i++)
        dp[i] = dp[i >> 1] + (i & 1);
    return dp;
}
// Time: O(n), Space: O(n)
// dp[6=0b110]: dp[3=0b11] + 0 = 2 + 0 = 2 ✓ (6 has 2 set bits)
// dp[7=0b111]: dp[3=0b11] + 1 = 2 + 1 = 3 ✓

23.6 Bitmask for Subset Enumeration

Enumerate All Subsets of n Elements

// Each integer in [0, 2^n - 1] represents a subset
// Bit k = 1 means element k is in the subset
int n = 4;
vector<int> elems = {a, b, c, d};
 
for (int mask = 0; mask < (1 << n); mask++) {
    vector<int> subset;
    for (int k = 0; k < n; k++) {
        if (mask & (1 << k)) subset.push_back(elems[k]);
    }
    process(subset);
}
// Total iterations: 2^n subsets, n bits each → O(n × 2^n)
// Practical for n ≤ 20 (2^20 ≈ 10^6)

Enumerate All Subsets of a Given Mask

// Enumerate ALL non-empty subsets of mask in decreasing order
for (int sub = mask; sub > 0; sub = (sub-1) & mask) {
    process(sub);
    // (sub-1) & mask: decrements sub within the bits of mask
}
// Also process empty subset if needed: process(0)
 
// Example: mask = 0b1010 (elements at positions 1 and 3)
// Subsets: 0b1010 (both), 0b1000 (only 3), 0b0010 (only 1)
// Iteration: sub=1010 → sub=(1001)&1010=1000 → sub=(0111)&1010=0010 → sub=0001&1010=0 (stop)

Enumerate All k-element Subsets (Gosper's Hack)

// Enumerate all bitmasks with exactly k bits set, starting from the smallest
void enumerateKSubsets(int n, int k) {
    int mask = (1 << k) - 1;  // k consecutive ones: 0b...0111 (k ones)
 
    while (mask < (1 << n)) {
        process(mask);
 
        // Gosper's hack: next permutation of set bits
        int c = mask & (-mask);         // Lowest set bit
        int r = mask + c;               // Right bits carry
        mask = (((r ^ mask) >> 2) / c) | r;
    }
}
// Iterates all C(n, k) subsets in O(1) per step

Meet in the Middle — Using Subset Enumeration

// For n ≤ 40: split into two halves of n/2, enumerate each half's subsets,
// then combine. Reduces 2^n to 2 × 2^(n/2) = 2^(n/2+1).
 
// Example: Partition Array Into Two Equal-Sum Halves
// Half 1: generate all 2^20 subset sums → store in hash set
// Half 2: for each subset sum s, check if (target - s) exists in hash set

23.7 Power of 2 Tricks

Check if n is a Power of 2

bool isPowerOf2(int n) {
    return n > 0 && (n & (n-1)) == 0;
    // n-1 flips the lowest set bit and all below it
    // If n is a power of 2: n = 1000...0, n-1 = 0111...1
    // n & (n-1) = 0 ✓
    // If not: some high bit and low bits remain → n & (n-1) != 0
}
 
// Powers of 2 up to 32 bits: 1,2,4,8,...,2^30,2^31(as unsigned)

Next Power of 2 (≥ n)

// Round up n to the next power of 2
unsigned nextPow2(unsigned n) {
    if (n == 0) return 1;
    n--;           // Handle exact powers of 2
    n |= n >> 1;   // Fill in below MSB
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    return n + 1;
}
// Or simply: 1 << (32 - __builtin_clz(n-1))  for n > 1
// Example: n=6 → 8; n=8 → 8; n=9 → 16

Useful Power-of-2 Expressions

// Fast modulo by power of 2:
int mod = n % 8;        // Slow: division
int fastMod = n & 7;    // Fast: n & (8-1) = n & 0b111
 
// Fast division by power of 2:
int div = n / 4;        // Slow
int fastDiv = n >> 2;   // Fast: right shift by log2(4) = 2
 
// Fast multiplication by power of 2:
int mul = n * 16;       // Standard
int fastMul = n << 4;   // Fast: left shift by log2(16) = 4
 
// Align n to next multiple of power of 2 (e.g., align to 16):
int aligned = (n + 15) & ~15;  // Round up to multiple of 16

23.8 Bit DP Patterns

Sum of Two Integers Without + or - (LC 371)

int getSum(int a, int b) {
    while (b != 0) {
        int carry = (unsigned int)(a & b) << 1;  // Carry bits
        a = a ^ b;                                // Sum without carry
        b = carry;                                // Process carry next round
    }
    return a;
}
// Terminates because each iteration reduces set bits in b (carry shifts left,
// so eventually carry bits "fall off" the top)
// Time: O(1) — at most 32 iterations, Space: O(1)

Hamming Distance and Total Hamming Distance

// Hamming distance between two integers: number of differing bit positions
int hammingDistance(int x, int y) {
    return __builtin_popcount(x ^ y);
}
 
// Total Hamming distance across all pairs in an array
// Instead of O(n²), for each bit position k:
// contribution = (count of 0s at k) × (count of 1s at k)
int totalHammingDistance(vector<int>& nums) {
    int total = 0;
    for (int k = 0; k < 32; k++) {
        int ones = 0;
        for (int x : nums) ones += (x >> k) & 1;
        int zeros = nums.size() - ones;
        total += ones * zeros;    // Each (0,1) pair contributes 1 to distance
    }
    return total;
}
// Time: O(32n) = O(n), Space: O(1)

Bitwise AND of Numbers Range (LC 201)

// AND of all numbers from m to n:
// Any two consecutive numbers n and n+1 differ in at least the lowest bit
// So AND of a range [m..n] = common prefix of m and n in binary
int rangeBitwiseAnd(int left, int right) {
    int shift = 0;
    while (left != right) {
        left >>= 1; right >>= 1;
        shift++;
    }
    return left << shift;  // Restore to original bit position
}
// Time: O(log n), Space: O(1)
// Explanation: m and n share a common bit prefix; all lower bits become 0
// because somewhere in [m,n] those bits have been 0 (by the range traversal)

Single Number II — Numbers Appearing 3 Times (LC 137)

// All numbers appear 3 times EXCEPT one (appears once).
// For each bit position, count total 1s. If count % 3 == 1 → single has this bit.
int singleNumber(vector<int>& nums) {
    int ones = 0, twos = 0;
    for (int x : nums) {
        ones  = (ones ^ x) & ~twos;   // Add to 'ones' if not already in 'twos'
        twos  = (twos ^ x) & ~ones;   // Add to 'twos' if not already in 'ones'
        // When count reaches 3: 'ones' and 'twos' both clear this bit
    }
    return ones;   // 'ones' holds bits that appeared exactly once (mod 3)
}
// Time: O(n), Space: O(1) — bit counter automaton

23.9 Sample Problems


Problem 1 — Single Number (Easy) — LC 136

int singleNumber(vector<int>& nums) {
    int result = 0;
    for (int x : nums) result ^= x;
    return result;
}
// Time: O(n), Space: O(1)
// XOR: all duplicates cancel (a^a=0), single survives (0^b=b)

Problem 2 — Missing Number (Easy) — LC 268

int missingNumber(vector<int>& nums) {
    int n = nums.size(), result = n;
    for (int i = 0; i < n; i++) result ^= i ^ nums[i];
    return result;
}
// Time: O(n), Space: O(1)
// Alternative: n*(n+1)/2 - accumulate(nums)

Problem 3 — Number of 1 Bits (Easy) — LC 191

int hammingWeight(uint32_t n) {
    int count = 0;
    while (n) { n &= (n-1); count++; }   // Brian Kernighan: clear LSB each time
    return count;
    // Or: return __builtin_popcount(n);
}
// Time: O(set bits), Space: O(1)

Problem 4 — Counting Bits (Easy) — LC 338

vector<int> countBits(int n) {
    vector<int> dp(n+1, 0);
    for (int i = 1; i <= n; i++)
        dp[i] = dp[i>>1] + (i&1);   // Right-shift removes last bit, add it back
    return dp;
}
// Time: O(n), Space: O(n)

Problem 5 — Reverse Bits (Easy) — LC 190

uint32_t reverseBits(uint32_t n) {
    uint32_t result = 0;
    for (int i = 0; i < 32; i++) {
        result = (result << 1) | (n & 1);   // Shift result left, append LSB of n
        n >>= 1;
    }
    return result;
}
// Time: O(32) = O(1), Space: O(1)
 
// Divide & conquer bit reversal (O(1) with no loop):
uint32_t reverseBits_DC(uint32_t n) {
    n = (n >> 16) | (n << 16);                     // Swap 16-bit halves
    n = ((n & 0xff00ff00) >> 8) | ((n & 0x00ff00ff) << 8);   // Swap bytes
    n = ((n & 0xf0f0f0f0) >> 4) | ((n & 0x0f0f0f0f) << 4);  // Swap nibbles
    n = ((n & 0xcccccccc) >> 2) | ((n & 0x33333333) << 2);   // Swap 2-bit pairs
    n = ((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1);   // Swap adjacent bits
    return n;
}

Problem 6 — Power of Two (Easy) — LC 231

bool isPowerOfTwo(int n) {
    return n > 0 && (n & (n-1)) == 0;
}
// Time: O(1), Space: O(1)
// Power of 2 has exactly one set bit → clearing LSB gives 0

Problem 7 — Single Number II (Medium) — LC 137

int singleNumber(vector<int>& nums) {
    int ones = 0, twos = 0;
    for (int x : nums) {
        ones  = (ones ^ x) & ~twos;
        twos  = (twos ^ x) & ~ones;
    }
    return ones;
}
// Time: O(n), Space: O(1)
// Bit automaton: tracks count mod 3 per bit position

Problem 8 — Single Number III (Medium) — LC 260

vector<int> singleNumber(vector<int>& nums) {
    int xorAll = 0;
    for (int x : nums) xorAll ^= x;          // xorAll = a ^ b
    int diffBit = xorAll & (-xorAll);         // Lowest differing bit between a and b
    int a = 0, b = 0;
    for (int x : nums) {
        if (x & diffBit) a ^= x;             // Group 1
        else             b ^= x;             // Group 2
    }
    return {a, b};
}
// Time: O(n), Space: O(1)

Problem 9 — Sum of Two Integers (Medium) — LC 371

int getSum(int a, int b) {
    while (b) {
        int carry = (unsigned int)(a & b) << 1;
        a ^= b;
        b = carry;
    }
    return a;
}
// Time: O(1) — max 32 iterations, Space: O(1)

Problem 10 — Bitwise AND of Numbers Range (Medium) — LC 201

int rangeBitwiseAnd(int left, int right) {
    int shift = 0;
    while (left != right) { left >>= 1; right >>= 1; shift++; }
    return left << shift;
}
// Time: O(log n), Space: O(1)
// Keep shifting right until both are equal (common prefix), then shift back

Problem 11 — Total Hamming Distance (Medium) — LC 477

int totalHammingDistance(vector<int>& nums) {
    int total = 0;
    for (int k = 0; k < 32; k++) {
        int ones = 0;
        for (int x : nums) ones += (x >> k) & 1;
        total += ones * ((int)nums.size() - ones);
    }
    return total;
}
// Time: O(32n) = O(n), Space: O(1)

Problem 12 — Subsets via Bitmask (Medium) — LC 78

vector<vector<int>> subsets(vector<int>& nums) {
    int n = nums.size();
    vector<vector<int>> result;
    for (int mask = 0; mask < (1 << n); mask++) {
        vector<int> sub;
        for (int k = 0; k < n; k++)
            if (mask & (1 << k)) sub.push_back(nums[k]);
        result.push_back(sub);
    }
    return result;
}
// Time: O(n × 2^n), Space: O(n × 2^n)
// Compact alternative to backtracking; practical for n ≤ 20

23.10 LeetCode Problem Set

# Problem Difficulty Core Technique Link
1 Single Number Easy XOR all — pairs cancel LC 136
2 Number of 1 Bits Easy Brian Kernighan / popcount LC 191
3 Counting Bits Easy DP: dp[i] = dp[i>>1] + (i&1) LC 338
4 Reverse Bits Easy Shift & extract each bit LC 190
5 Single Number II Medium Bit automaton (ones, twos) LC 137
6 Single Number III Medium XOR split by differing bit LC 260
7 Sum of Two Integers Medium XOR + carry loop LC 371
8 Bitwise AND of Numbers Range Medium Find common bit prefix LC 201

Suggested order: #1 (Single Number) — the XOR gateway problem. #2 and #3 (Hamming Weight, Counting Bits) — master popcount and the dp[i>>1] trick. #4 (Reverse Bits) — bit extraction loop. #5 (Single Number II) — the ones/twos automaton is non-obvious but elegant. #6 (Single Number III) — two-group XOR split. #7 (Sum Without +) — carry propagation via XOR. #8 (AND of Range) — common prefix insight.


← PreviousChapter 22: Divide & ConquerNext →Chapter 24: Math & Number Theory
Chapter 23 — Progress
0 / 26 COMPLETE
Binary Representation
  • Know Two's Complement: -n = ~n + 1; -1 = all 1s
  • Know overflow: INT_MAX + 1 = INT_MIN
  • Use `1LL << k` (not `1 << k`) for shifts of 31 or more bits
Bitwise Operators
  • Know operator precedence: `~` > shifts > `&` > `^` > `|` — always parenthesize
  • Know: signed right shift fills with sign bit (arithmetic shift)
  • Know the six operators and their primary use cases
Essential Bit Tricks (from memory)
  • Check bit k: `(n >> k) & 1`
  • Set bit k: `n | (1 << k)`
  • Clear bit k: `n & ~(1 << k)`
  • Toggle bit k: `n ^ (1 << k)`
  • Isolate LSB: `n & (-n)`
  • Clear LSB: `n & (n - 1)`
  • All built-ins: `__builtin_popcount`, `__builtin_ctz`, `__builtin_clz`
XOR Magic
  • Know all four XOR properties (identity, self-inverse, commutative, associative)
  • Apply "XOR all" to find single element in array of pairs
  • Apply "split by differing bit" to find two single elements (LC 260)
Counting Set Bits
  • Implement Brian Kernighan: `while(n){n&=(n-1);count++;}`
  • Know: `n & (n-1)` always clears the LOWEST set bit
  • Implement counting bits DP: `dp[i] = dp[i>>1] + (i&1)`
  • Know total set bits from 1 to n using cycle formula
Subset Enumeration
  • Enumerate all 2^n subsets: `for mask in [0, 1<<n)`
  • Enumerate subsets of a mask: `for sub = mask; sub > 0; sub = (sub-1) & mask`
  • Know meet-in-the-middle: enumerate 2^(n/2) each half, combine
Power of 2
  • Check power of 2: `n > 0 && (n & (n-1)) == 0`
  • Fast modulo by 2^k: `n & (2^k - 1)`
  • Align to next multiple of 2^k: `(n + k-1) & ~(k-1)`
Notes▸