Lessons
1 · Foundations (Think Like a Programmer)
2 · Data Structures and Recursion
- 05Arrays and Array-Based Problem Solving
- 06Strings: Characters, Immutability, and Efficient Manipulation
- 07Linked Lists: Nodes, Pointers, and Pointer Reasoning
- 08Stacks and Queues
- 09Hashing: HashMap, HashSet, and Frequency Patterns
- 10Recursion and Backtracking
- 11Trees and Binary Search Trees
- 12Heaps and Priority Queues
3 · Core Algorithms and Reusable Patterns
4 · Advanced Interview Topics
Lesson 18 · Core Algorithms and Reusable Patterns
Bit Manipulation and Mathematical Patterns
Binary numbers, the bitwise operators and Java's signed-integer behaviour, then the reasoned uses: parity, powers of two, counting bits, XOR, masks and subsets, plus GCD, modular arithmetic, fast exponentiation and the sieve of Eratosthenes.
For
Learners who have covered the core data structures and want to understand binary numbers, bitwise operators and the elementary maths used in interviews.
Level
Before you start
Articles 3, 4 and 10: Java integer types and overflow, Big O, and recursion for the subset ideas. No prior knowledge of binary is needed.
On this page
Key takeaways
- Java ints are 32-bit two's complement numbers. Negative numbers have a leading 1, and shifting right has two forms: >> keeps the sign and >>> fills with zeros.
- The operators & | ^ ~ << >> let you test, set, clear and toggle individual bits. Always put parentheses around bit expressions.
- n & (n - 1) removes the lowest set bit. That one fact gives the power-of-two check and a fast bit counter.
- XOR cancels equal values (a ^ a is 0), so XOR-ing everything finds the single unpaired number. A bitmask of n bits can stand for a subset of n items.
- Euclid's algorithm gives the GCD in O(log n). Use long and Math.floorMod for modular arithmetic, and binary exponentiation for powers.
Every number in a computer is stored as a row of switches that are either off or on: a bit is a 0 or a 1. Normally you never see this. You write 13 and the machine handles the rest. But sometimes working at the level of the bits themselves is the neatest way to solve a problem. You can pack eight true/false flags into one number. You can find the one unpaired value in a list without a hash map. You can walk through every subset of a set with a single loop.
This article is about those uses. It is deliberately not a collection of magic one-liners to memorise. For every trick you will see why it works, because a trick you cannot explain is a liability in an interview. We also cover a few elementary mathematical tools that show up constantly: GCD, modular arithmetic, fast exponentiation, and primes.
What you will learn
#- How integers look in binary, including negative numbers in Java.
- The bitwise operators and shifts, and Java's specific rules about them.
- Reasoned uses: parity, testing and changing bits, powers of two, counting bits, XOR, masks and subsets.
- GCD and LCM, modular arithmetic, fast exponentiation, and the sieve of Eratosthenes.
- Which of these are essential, and which are rare.
Binary numbers
#In binary, each position is worth a power of two, counting from the right: 1, 2, 4, 8, 16, and so on. So 1101 in binary means 8 + 4 + 0 + 1 = 13.
A Java int has 32 bits. The leftmost bit is the sign bit. Java stores negative numbers in two's complement, where -x is ~x + 1: flip every bit of x, then add one. This has a useful consequence: the same addition hardware works for positive and negative numbers.
Because of that scheme, an int ranges from -2^31 to 2^31 - 1. The range is lopsided, so there is one more negative number than positive: Integer.MIN_VALUE has no positive counterpart. That is why -Integer.MIN_VALUE is still Integer.MIN_VALUE, and Math.abs(Integer.MIN_VALUE) is negative, as we noted in the Java article.
The operators
#Here is what each operator does. All of them work on every bit position at the same time.
a & b(AND): each result bit is 1 only if both input bits are 1.a | b(OR): each result bit is 1 if at least one input bit is 1.a ^ b(XOR, exclusive or): each result bit is 1 if the two input bits differ.~a(NOT): flips every bit.a << k(left shift): moves all bitskplaces to the left, filling with zeros on the right. This multiplies by2^k(unless bits fall off the left end).a >> k(arithmetic right shift): moves bits right, and copies the sign bit into the empty places on the left. For positive numbers this is a division by2^k(rounded down), and for negative numbers it keeps the number negative.a >>> k(logical right shift): moves bits right and fills the left with zeros, so the result is never negative fork > 0.
// File: Main.javapublic class Main { public static void main(String[] args) { int a = 12; // binary 1100 int b = 10; // binary 1010 System.out.println(Integer.toBinaryString(a) + " " + Integer.toBinaryString(b)); System.out.println((a & b) + " " + (a | b) + " " + (a ^ b) + " " + (~a)); System.out.println((a << 2) + " " + (a >> 2) + " " + (-8 >> 1) + " " + (-8 >>> 1)); System.out.println(Integer.toBinaryString(-5)); System.out.println((1 << 32) + " " + (1 << 31) + " " + (1L << 40)); System.out.println((-Integer.MIN_VALUE == Integer.MIN_VALUE) + " " + Math.abs(Integer.MIN_VALUE)); int n = 6; System.out.println((n & 1) == 0); // the parentheses matter, see below }}1100 10108 14 6 -1348 3 -4 2147483644111111111111111111111111111110111 -2147483648 1099511627776true -2147483648trueLet us read the output carefully, because each line contains a Java-specific lesson.
12 & 10is1000(8): only the leftmost bit is on in both.12 | 10is1110(14).12 ^ 10is0110(6): they differ in two positions.~12is-13, because~x = -x - 1in two's complement.12 << 2is48(twelve times four).12 >> 2is3.-8 >> 1is-4: the sign is preserved.-8 >>> 1is2147483644: the sign bit becomes a zero, so the result is a huge positive number.-5is printed as 32 bits starting with many ones. That is two's complement:~5 + 1.- Shift counts wrap. For an
int, only the low 5 bits of the shift count are used, so1 << 32is1 << 0, which is1, not0.1 << 31isInteger.MIN_VALUE, because the 1 lands in the sign bit. To get bit 40, you need along:1L << 40. A shift count ofk >= 32on anintis a classic bug. - Precedence.
==binds more tightly than&, son & 1 == 0meansn & (1 == 0), which does not compile. Always parenthesise:(n & 1) == 0.
Basic bit operations
#Positions are counted from 0 at the right. A mask is a number with the bits you care about switched on. Using 1 << i as a mask for position i:
Task | Expression |
|---|---|
Is bit |
|
Set bit |
|
Clear bit |
|
Toggle bit |
|
Is |
|
For parity, the lowest bit is the "ones" place, and since every other place is even, a number is odd exactly when its lowest bit is 1. This works for negative numbers too, in two's complement. (n % 2 == 1 fails for negative odd numbers, because -3 % 2 is -1.)
Why n & (n - 1) is useful
#Subtracting 1 from n flips the lowest set bit to 0 and flips every bit below it to 1. For example, 1100 minus 1 is 1011. When you AND them, everything above the lowest set bit is unchanged and everything from that bit down is cleared:
n = 1 1 0 0n - 1 = 1 0 1 1n & (n-1) = 1 0 0 0 <- the lowest set bit (the middle 1) has been removedSo n & (n - 1) removes the lowest set bit. Two uses follow.
Power-of-two check. A positive power of two has exactly one set bit. Removing it leaves zero. So n > 0 && (n & (n - 1)) == 0. The n > 0 part matters: for n = 0 the AND is also zero, and for Integer.MIN_VALUE (a single set bit, the sign bit) the AND is zero too, but neither is a positive power of two.
Counting set bits (Brian Kernighan's method). Repeat n &= n - 1 until n is zero, counting the iterations. Each iteration removes one set bit, so the loop runs exactly as many times as there are 1s, rather than 32 times. Dry run for 13 (1101):
Step |
|
|
| Count |
|---|---|---|---|---|
1 |
|
|
| 1 |
2 |
|
|
| 2 |
3 |
|
|
| 3 |
Java also provides Integer.bitCount(n), which you should use in real code. Knowing the loop explains how it works.
XOR: the "cancel out" operator
#XOR has three properties that make it special:
a ^ a = 0: a value cancels itself.a ^ 0 = a: zero changes nothing.- It is commutative and associative, so the order does not matter.
So if you XOR a list in which every value appears twice except one, all the pairs cancel to zero and only the single value remains. This solves "single number" in O(n) time and O(1) space, with no hash set.
The same idea finds a missing number. Take the numbers 0 to n with one missing. XOR together all the numbers 0..n and all the array elements. Every number that is present appears twice and cancels, so only the missing one remains. Unlike the sum formula in the arrays article, there is no overflow to worry about.
Bitmasks: sets and subsets
#A number with n bits can represent a subset of n items: bit i is 1 if item i is in the subset. This is compact (one int holds up to 32 flags), fast, and it lets you enumerate all subsets with a simple loop: every number from 0 to 2^n - 1 is a different subset.
A related use is a visited set for letters. For lowercase letters, 26 bits are enough, so one int can record which letters you have seen: seen |= 1 << (c - 'a'). Testing whether a letter was seen is (seen & (1 << (c - 'a'))) != 0.
The lowest set bit of n is n & -n. In two's complement, -n is ~n + 1, which flips everything above the lowest set bit and keeps that bit and the zeros below it. ANDing with n leaves only that one bit. We will use this idea in the Fenwick tree in Range Queries and Disjoint Set Union.
// File: Main.javaimport java.util.ArrayList;import java.util.List;public class Main { static boolean isPowerOfTwo(int n) { return n > 0 && (n & (n - 1)) == 0; } static int countBitsLoop(int n) { int count = 0; while (n != 0) { count += n & 1; n >>>= 1; // unsigned shift, so a negative number also reaches zero } return count; } static int countBitsKernighan(int n) { int count = 0; while (n != 0) { n &= n - 1; // remove the lowest set bit count++; } return count; } static int singleNumber(int[] nums) { // every other value appears exactly twice int result = 0; for (int v : nums) { result ^= v; } return result; } static int missingNumber(int[] nums) { // nums holds 0..n with one value missing int result = nums.length; for (int i = 0; i < nums.length; i++) { result ^= i ^ nums[i]; } return result; } static boolean allUniqueLowercase(String s) { int seen = 0; for (char c : s.toCharArray()) { int bit = 1 << (c - 'a'); if ((seen & bit) != 0) { return false; } seen |= bit; } return true; } static List<String> subsetsByMask(String[] items) { List<String> result = new ArrayList<>(); for (int mask = 0; mask < (1 << items.length); mask++) { StringBuilder subset = new StringBuilder("{"); for (int i = 0; i < items.length; i++) { if ((mask & (1 << i)) != 0) { if (subset.length() > 1) { subset.append(','); } subset.append(items[i]); } } result.add(subset.append('}').toString()); } return result; } public static void main(String[] args) { int[] powers = {1, 2, 3, 16, 0, -8, Integer.MIN_VALUE, 1 << 30}; StringBuilder out = new StringBuilder(); for (int p : powers) { out.append(isPowerOfTwo(p)).append(' '); } System.out.println(out.toString().trim()); int[] numbers = {0, 1, 13, 255, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}; for (int n : numbers) { int a = countBitsLoop(n); int b = countBitsKernighan(n); int c = Integer.bitCount(n); System.out.println(n + ": " + a + " " + b + " " + c + " " + (a == b && b == c)); } int n = 10; // binary 1010 System.out.println(((n >> 1) & 1) + " " + (n | (1 << 0)) + " " + (n & ~(1 << 1)) + " " + (n ^ (1 << 3)) + " " + (12 & -12)); System.out.println(singleNumber(new int[] {4, 1, 2, 1, 2}) + " " + singleNumber(new int[] {7}) + " " + missingNumber(new int[] {3, 0, 1})); System.out.println(allUniqueLowercase("abcde") + " " + allUniqueLowercase("hello") + " " + allUniqueLowercase("")); System.out.println(subsetsByMask(new String[] {"a", "b", "c"})); }}true true false true false false false true0: 0 0 0 true1: 1 1 1 true13: 3 3 3 true255: 8 8 8 true-1: 32 32 32 true-2147483648: 1 1 1 true2147483647: 31 31 31 true1 11 8 2 44 7 2true false true[{}, {a}, {b}, {a,b}, {c}, {a,c}, {b,c}, {a,b,c}]Notes on the output:
Integer.MIN_VALUEis not a power of two, even though it has a single set bit, thanks to then > 0check.- All three bit counters agree for every test, including
-1(all 32 bits set) andInteger.MIN_VALUE(just the sign bit). IncountBitsLoop, we used>>>=rather than>>=: an arithmetic shift would keep a negative number negative forever and the loop would never end. - On the next line, for
n = 10(1010): bit 1 is1; setting bit 0 gives11; clearing bit 1 gives8; toggling bit 3 gives2; and12 & -12is4, the lowest set bit of1100. - The subsets appear in the order of the mask: mask
3(binary011) is{a,b}, and mask4(100) is{c}. There are2^3 = 8of them.
Complexity. The power check, bit tests and single-bit operations are O(1). Counting bits is O(number of set bits) with Kernighan's method, or O(32). Single number and missing number are O(n) time and O(1) space. Enumerating all subsets with masks is O(n × 2^n), so it is only suitable for n up to about 20.
Mathematical patterns
#Greatest common divisor (Euclid's algorithm)
#The GCD of two numbers is the largest number that divides both. Euclid's insight: the common divisors of a and b are exactly the common divisors of b and a mod b. So replace (a, b) by (b, a % b) until the second number is zero. The first number is then the GCD.
Dry run for gcd(48, 18):
|
|
| Next pair |
|---|---|---|---|
48 | 18 | 12 |
|
18 | 12 | 6 |
|
12 | 6 | 0 |
|
6 | 0 | stop, GCD = 6 |
Each step makes the numbers shrink quickly (every two steps, a at least halves), so the time is O(log(min(a, b))).
The LCM (least common multiple) is a / gcd(a, b) × b. Divide first and multiply second: a * b / gcd can overflow even when the answer fits.
Modular arithmetic
#Many problems ask for an answer "modulo 1,000,000,007" because the true answer is huge. The rules: (a + b) % m = ((a % m) + (b % m)) % m, and the same for multiplication. Reduce after each operation to keep numbers small. With m around a billion, the product of two reduced values is below about 10^18, which fits in a long, so use long. Also remember that Java's % can return a negative number for a negative left operand, so use Math.floorMod when a non-negative remainder is needed.
Fast exponentiation
#Computing base^exp by multiplying exp times is too slow for huge exponents. Binary exponentiation uses the bits of the exponent: x^13 = x^8 × x^4 × x^1 because 13 = 1101 in binary. Keep squaring base, and multiply it into the result whenever the current lowest bit of the exponent is 1. That takes O(log exp) multiplications. This is the iterative cousin of the recursive power from the recursion article.
Dry run for modPow(3, 13, 1000):
Round |
| Lowest bit |
|
|
|---|---|---|---|---|
1 | 13 ( | 1 |
| 9 |
2 | 6 ( | 0 | 3 | 81 |
3 | 3 ( | 1 |
|
|
4 | 1 ( | 1 |
|
So 3^13 mod 1000 = 323. (Indeed 3^13 = 1,594,323.)
Primes and the sieve of Eratosthenes
#Checking one number for primality takes O(sqrt(n)), as in the Java article. To find all primes below n, the sieve of Eratosthenes is far faster. Make a table of n flags, all "might be prime". Walk upward; when you reach a number not yet crossed out, it is prime: cross out all its multiples, starting from its square (smaller multiples were already crossed out by smaller primes). The time is O(n log log n), which is nearly linear, and the space is O(n).
// File: Main.javaimport java.math.BigInteger;public class Main { static long gcd(long a, long b) { a = Math.abs(a); b = Math.abs(b); while (b != 0) { long remainder = a % b; a = b; b = remainder; } return a; } static long lcm(long a, long b) { if (a == 0 || b == 0) { return 0; } return Math.abs(a / gcd(a, b) * b); // divide first to keep the number small } static long modPow(long base, long exp, long mod) { long result = 1 % mod; base %= mod; while (exp > 0) { if ((exp & 1) == 1) { result = result * base % mod; } base = base * base % mod; exp >>= 1; } return result; } // Number of primes strictly less than n. static int countPrimes(int n) { boolean[] composite = new boolean[Math.max(n, 0)]; int count = 0; for (int i = 2; i < n; i++) { if (!composite[i]) { count++; for (long j = (long) i * i; j < n; j += i) { // long: i * i can overflow int composite[(int) j] = true; } } } return count; } static boolean isPrime(int n) { if (n < 2) { return false; } for (int i = 2; i <= n / i; i++) { if (n % i == 0) { return false; } } return true; } public static void main(String[] args) { System.out.println(gcd(48, 18) + " " + gcd(0, 5) + " " + gcd(17, 5) + " " + gcd(-12, 18)); System.out.println(lcm(4, 6) + " " + lcm(0, 3) + " " + lcm(1_000_000_007L, 998_244_353L)); System.out.println(modPow(2, 10, 1000) + " " + modPow(3, 13, 1000) + " " + modPow(5, 0, 7)); long big = modPow(2, 1_000_000, 1_000_000_007L); long reference = BigInteger.valueOf(2).modPow(BigInteger.valueOf(1_000_000), BigInteger.valueOf(1_000_000_007L)).longValue(); System.out.println("modPow matches BigInteger: " + (big == reference)); System.out.println(countPrimes(0) + " " + countPrimes(2) + " " + countPrimes(10) + " " + countPrimes(100) + " " + countPrimes(1000)); int slowCount = 0; for (int i = 0; i < 1000; i++) { if (isPrime(i)) { slowCount++; } } System.out.println("sieve agrees with trial division below 1000: " + (slowCount == countPrimes(1000))); }}6 5 1 612 0 99824435998771047124 323 1modPow matches BigInteger: true0 0 4 25 168sieve agrees with trial division below 1000: trueThe two outputs that check one method against another (modPow against BigInteger, and the sieve against trial division) are a good habit: a simple, slow, obviously correct version is a great test for the fast one.
Complexity: gcd is O(log min(a, b)); lcm is the same plus a multiplication; modPow is O(log exp); the sieve is O(n log log n) time and O(n) space.
Complexity summary
#Technique | Time | Extra space |
|---|---|---|
Single bit test, set, clear, toggle | O(1) | O(1) |
Power of two | O(1) | O(1) |
Count set bits (Kernighan) | O(set bits) | O(1) |
Single number, missing number with XOR | O(n) | O(1) |
All subsets by mask | O(n × 2^n) | O(1) beyond the output |
GCD | O(log min(a, b)) | O(1) |
Modular exponentiation | O(log exp) | O(1) |
Sieve of Eratosthenes up to | O(n log log n) | O(n) |
Edge cases and common mistakes
#- Missing parentheses.
n & 1 == 0does not mean what it appears to. Write(n & 1) == 0. - Shifting an
intby 32 or more. The count wraps. Uselongand1L <<. >>versus>>>on negative numbers.>>keeps the sign. Use>>>for an unsigned shift.- Using
1 << kas a mask for alongvalue whenk >= 32. The mask is computed as anint, so it is wrong. Use1L << k. n % 2 == 1for oddness. It fails for negative numbers. Use(n & 1) != 0.- Forgetting
n > 0in the power-of-two test. - Overflow in math.
a * binint,a * b / gcdbefore dividing, andi * iin a sieve. Uselongand divide first. Math.abs(Integer.MIN_VALUE)is negative.- Negative remainders. Use
Math.floorModwhen you need0..m-1. - Over-cleverness. Do not use a bit trick when a plain expression is clearer, unless you are sure everyone reading it will follow.
Exercises
#Exercise 1
Trace
#Let x = 22 (binary 10110). Compute x & (x - 1), x & -x, x >> 1, and the number of set bits. Show the binary for each.
Exercise 2: Hamming distance
#The Hamming distance between two integers is the number of bit positions where they differ. For 1 (0001) and 4 (0100) it is 2. Write a method for any two ints.
Exercise 3: Counting bits for every number
#Given n, return an array where element i is the number of set bits in i, for i from 0 to n. Do it in O(n) total, not O(n log n).
Exercise 4: Debug
#This code has bugs: isPowerOfTwoBuggy gives wrong answers for two inputs, and getBitLong gives the wrong answer for high bit positions. Find them and fix them.
static boolean isPowerOfTwoBuggy(int n) { return (n & (n - 1)) == 0;}static int getBitLong(long n, int k) { return (n & (1 << k)) != 0 ? 1 : 0;}Exercise 5: Interview-style, two single numbers
#An array contains numbers that each appear twice, except for exactly two numbers that appear once each. Find those two numbers in O(n) time and O(1) extra space. For [1, 2, 1, 3, 2, 5] the answer is 3 and 5.
Show solutionHide solution
x = 22 is 10110 in binary. x - 1 = 21 is 10101.
Expression | Binary work | Result |
|---|---|---|
|
|
|
| the lowest set bit of |
|
|
|
|
Set bits |
|
|
// File: Main.javapublic class Main { public static void main(String[] args) { int x = 22; System.out.println(Integer.toBinaryString(x) + " " + Integer.toBinaryString(x - 1)); System.out.println((x & (x - 1)) + " " + (x & -x) + " " + (x >> 1) + " " + Integer.bitCount(x)); }}10110 1010120 2 11 3Exercise 2
Show solutionHide solution
x ^ y has a 1 exactly where x and y differ, so the answer is the number of set bits in x ^ y. Use Integer.bitCount, or Kernighan's loop. Time O(1) (at most 32 bits). This works for negative numbers too, because the comparison is bit by bit.
// File: Main.javapublic class Main { static int hammingDistance(int x, int y) { return Integer.bitCount(x ^ y); } public static void main(String[] args) { System.out.println(hammingDistance(1, 4)); System.out.println(hammingDistance(3, 1)); System.out.println(hammingDistance(7, 7)); System.out.println(hammingDistance(0, -1)); System.out.println(hammingDistance(Integer.MIN_VALUE, Integer.MAX_VALUE)); }}21032320 and -1 differ in all 32 positions, and Integer.MIN_VALUE (1000...0) and Integer.MAX_VALUE (0111...1) are exact opposites, so those also give 32.
Exercise 3
Show solutionHide solution
Use a recurrence on earlier answers: the bits of i are the bits of i >> 1 (that is, i without its last bit), plus the last bit i & 1. So bits[i] = bits[i >> 1] + (i & 1). Since i >> 1 is smaller than i, it was already computed. Each entry costs O(1), so the total is O(n). This is dynamic programming in a very simple form.
// File: Main.javaimport java.util.Arrays;public class Main { static int[] countBits(int n) { int[] bits = new int[n + 1]; for (int i = 1; i <= n; i++) { bits[i] = bits[i >> 1] + (i & 1); } return bits; } public static void main(String[] args) { System.out.println(Arrays.toString(countBits(5))); System.out.println(Arrays.toString(countBits(0))); int[] big = countBits(1000); boolean ok = true; for (int i = 0; i <= 1000; i++) { ok &= big[i] == Integer.bitCount(i); } System.out.println("matches Integer.bitCount up to 1000: " + ok); }}[0, 1, 1, 2, 1, 2][0]matches Integer.bitCount up to 1000: trueExercise 4
Show solutionHide solution
isPowerOfTwoBuggyis missingn > 0. Forn = 0,0 & -1is0, so it returnstrue, but zero is not a power of two. ForInteger.MIN_VALUE,n - 1wraps toInteger.MAX_VALUE, and the AND is0, so it returnstrue, but a negative number is not a power of two.getBitLongbuilds the mask as anint.1 << kis anintshift, and shift counts wrap at 32, so1 << 40is1 << 8, which is256. The test then looks at the wrong bit. Use1L << k.
// File: Main.javapublic class Main { static boolean isPowerOfTwoBuggy(int n) { return (n & (n - 1)) == 0; } static boolean isPowerOfTwo(int n) { return n > 0 && (n & (n - 1)) == 0; } static int getBitLongBuggy(long n, int k) { return (n & (1 << k)) != 0 ? 1 : 0; } static int getBitLong(long n, int k) { return (n & (1L << k)) != 0 ? 1 : 0; } public static void main(String[] args) { System.out.println(isPowerOfTwoBuggy(0) + " " + isPowerOfTwo(0)); System.out.println(isPowerOfTwoBuggy(Integer.MIN_VALUE) + " " + isPowerOfTwo(Integer.MIN_VALUE)); long value = 1L << 40; System.out.println(getBitLongBuggy(value, 40) + " " + getBitLong(value, 40)); }}true falsetrue false0 1In each line, the first value is the buggy result and the second is the correct one. The long bug is particularly nasty because it only shows up when a high bit is the one that matters.
Exercise 5
Show solutionHide solution
Let the two unique numbers be a and b. XOR-ing the whole array cancels all the pairs and leaves x = a ^ b. Because a != b, x is not zero, so it has at least one set bit. At that bit, a and b differ: one has a 1 and the other a 0.
Pick any such bit, for example the lowest set bit x & -x. Now split all numbers into two groups by whether they have that bit set. Then:
aandbland in different groups.- Each pair of equal numbers lands in the same group (equal numbers have equal bits), so the pairs still cancel inside their group.
XOR each group separately, and each result is one of the unique numbers. Two passes over the array: O(n) time and O(1) space.
// File: Main.javapublic class Main { static int[] twoSingles(int[] nums) { int x = 0; for (int v : nums) { x ^= v; // x = a ^ b } int lowBit = x & -x; // a bit where a and b differ int a = 0; int b = 0; for (int v : nums) { if ((v & lowBit) != 0) { a ^= v; } else { b ^= v; } } return new int[] {Math.min(a, b), Math.max(a, b)}; } public static void main(String[] args) { int[] r1 = twoSingles(new int[] {1, 2, 1, 3, 2, 5}); int[] r2 = twoSingles(new int[] {-1, 0}); int[] r3 = twoSingles(new int[] {Integer.MIN_VALUE, 7, 3, 7}); System.out.println(r1[0] + " " + r1[1]); System.out.println(r2[0] + " " + r2[1]); System.out.println(r3[0] + " " + r3[1]); }}3 5-1 0-2147483648 3For [1, 2, 1, 3, 2, 5]: x = 3 ^ 5 = 6 (110), and the lowest set bit is 2 (010). Numbers with that bit set: 2, 3, 2. They XOR to 3. Numbers without it: 1, 1, 5, which XOR to 5. Even with the extreme value Integer.MIN_VALUE, x & -x still gives a valid single-bit mask, so the method works.
Where to go next
#- Next: Graphs: Representations, BFS, and DFS starts the advanced module with one of the most important structures of all.
- Later:
n & -nreturns in the Fenwick tree in Range Queries and Disjoint Set Union, and subset masks are a stepping stone to some dynamic programming problems. - Practice habit: whenever you use a bit trick, write a one-line comment that says what the bits mean ("lowest set bit", "letter seen"). If you cannot write that comment, use plain arithmetic instead.
Connected lessons
Linked from 3 guides
Other pages in the library that point here.
Keep reading
More in Technology →Guide · Technology
How to Learn Data Structures and Algorithms Without Burning Out
Guide · Career
The OA Landscape: Platforms, Formats, Proctoring and Rules
Guide · Career
A LinkedIn Profile That Gets Recruiter Messages

