Skip to content
dsa450for five year olds

Bit Manipulation · 2 of 10

Find the two non-repeating elements in an array of repeating elements

Every number in the list has a twin except two loners; cancel the twins with XOR, then split the loners apart by one bit.

Simple way
time O(n)
room O(n)
Clever way
time O(n)
room O(1)

The problem, simply

You get an array of numbers. Every number shows up exactly twice, except two numbers that show up only once. Find those two numbers and give them back, smaller one first.

The hard part is doing it with only a few extra variables, no map and no sorting.

A story to remember it

Picture it

array
012345247924
bits
01237011191001

The array 2, 4, 7, 9, 2, 4. The 2s and the 4s are twins. 7 and 9 are alone. In bits, 7 and 9 differ at bits 1, 2 and 3.

  • checking
  • answer

Examples

A normal case

In
[2, 4, 7, 9, 2, 4]
Out
[7, 9]

2 and 4 come twice. 7 and 9 come once.

Loners that differ only in the last bit

In
[1, 2, 3, 2, 1, 4]
Out
[3, 4]

3 ^ 4 = 7, whose lowest 1 is bit 0. Odd numbers 1, 3, 1 go one way and give 3. Even numbers 2, 2, 4 go the other way and give 4.

Only the loners

In
[5, 1]
Out
[1, 5]

No twins at all. XOR of everything is 5 ^ 1 = 4, and splitting by bit 2 puts 5 and 1 on different sides.

Zero is a loner

In
[0, 6, 3, 3]
Out
[0, 6]

XOR of all is 6. Splitting by bit 1 puts 6, 3, 3 on one side and 0 alone on the other. That side gives 0, which is a real answer, not a missing one.

Negative numbers

In
[-3, 5, 5, 8]
Out
[-3, 8]

The bit trick works the same on negative numbers, because it only looks at bits.

Way 1: the simple way

Count how many times each number shows up, then keep the ones with count 1.

  1. Walk the array and count each number in a hash map.
  2. Walk again and collect the numbers whose count is 1.
  3. Put the smaller one first.
java
import java.util.*;

class Solution {
    public static int[] findTwoNonRepeating(int[] arr) {
        Map<Integer, Integer> count = new HashMap<>();
        for (int x : arr) count.merge(x, 1, Integer::sum);   // tally each number
        List<Integer> alone = new ArrayList<>();
        for (int x : arr) {
            if (count.get(x) == 1) alone.add(x);              // shows up once
        }
        int a = alone.get(0), b = alone.get(1);
        return a < b ? new int[]{a, b} : new int[]{b, a};
    }
}

How long it takes: two walks, O(n). Extra room: the map holds about n / 2 numbers, O(n). Checking each number against all others with two loops would also work but takes O(n^2).

Way 2: the clever way

XOR has two rules: x ^ x = 0 and x ^ 0 = x. The order does not matter.

  1. XOR every number together. Twins cancel, so you get xor = a ^ b, where a and b are the loners.
  2. a and b are different, so xor has at least one 1. A 1 there means a and b differ at that bit. Keep the lowest one with xor & -xor.
  3. Split the array by that bit. Twins share all bits, so both twins land on the same side. a and b land on different sides.
  4. XOR each side on its own. Each side's twins cancel and leave one loner.

Cancel the twins, then split by one bit

01 / 13

array
012345247924
bits
0123xor = 00000

Every number comes in a pair except two. XOR has a magic rule: a number XOR itself is 0, and 0 XOR a number is that number. So pairs cancel out. Start with xor = 0.

xor
0
  • looking now
  • checking
  • seen
  • done
  • answer
  • rejected
  • path
  • just added
  • ignored
java
class Solution {
    public static int[] findTwoNonRepeating(int[] arr) {
        int xor = 0;
        for (int x : arr) xor ^= x;          // twins cancel, a ^ b is left

        int pick = xor & -xor;               // lowest bit where a and b differ

        int a = 0, b = 0;
        for (int x : arr) {
            if ((x & pick) != 0) a ^= x;     // team with that bit on
            else b ^= x;                     // team with that bit off
        }
        return a < b ? new int[]{a, b} : new int[]{b, a};
    }
}

How long it takes: two walks over the array, O(n). Extra room: four ints, O(1).

Watch out for

  • xor & -xor keeps the lowest 1. It works for every int, even when xor is Integer.MIN_VALUE.
  • Starting a or b at something other than 0. XOR with 0 changes nothing, so 0 is the only safe start.
  • Forgetting the order. The two loners come out in whatever order the split gives, so sort the pair if the problem asks.

What interviewers ask next

  • Only one loner and everything else in pairs? XOR all of them. The answer is what is left.
  • Every number shows up three times except one? Count the 1s at each of the 32 bit positions. Where the count is not a multiple of 3, the loner has a 1. That is O(32 n) time and O(1) room.
  • One number is missing from 1 to n and one is repeated? XOR the array with 1 to n. Now the two you want behave like loners, so split by a bit the same way.
  • Why does -x share only the lowest 1 with x? -x is ~x + 1. Adding 1 to ~x carries through the trailing 1s of ~x and stops exactly at the lowest 1 of x.

Remember this

Grown-up words: the mixing bowl is XOR, twins vanishing is x ^ x = 0, the thing the loners do not share is a set bit of a ^ b, and xor & -xor isolates the lowest set bit using two's complement.