single number algorithm def

This commit is contained in:
2025-09-08 14:00:13 +01:00
parent 3ef5b138f3
commit 6398b0eec8

View File

@@ -0,0 +1,628 @@
import type {
AlgorithmDefinition,
ArrayElementState,
BitState,
BitManipulationState,
} from '@/lib/visualizations/types';
/**
* Single Number - LeetCode 136
*
* Given a non-empty array where every element appears twice except for one,
* find that single element.
*
* Example: nums = [2, 2, 1]
* Output: 1
*
* Key insight: XOR all numbers together. Pairs cancel out (a ^ a = 0),
* leaving only the single number.
*/
// Helper to convert number to binary string
function toBinary(n: number, width: number = 4): string {
if (n < 0) {
// Handle negative numbers with two's complement representation
return (n >>> 0).toString(2).slice(-width);
}
return n.toString(2).padStart(width, '0');
}
// Helper to create array elements
function createArrayElements(
nums: number[],
highlightIndex?: number,
processedIndices: number[] = []
): ArrayElementState[] {
return nums.map((value, index) => ({
value,
index,
state:
index === highlightIndex
? 'highlighted'
: processedIndices.includes(index)
? 'dimmed'
: 'normal',
}));
}
// Helper to create bit state
function createBit(
id: string,
value: number,
state: BitState['state'],
label?: string
): BitState {
return {
id,
value,
bits: toBinary(value),
state,
label,
};
}
// Helper to create bit manipulation state
function createBitManip(
id: string,
operands: BitState[],
operation?: BitManipulationState['operation'],
result?: BitState,
label?: string
): BitManipulationState {
return { id, operands, operation, result, label };
}
// Input array
const NUMS = [2, 2, 1];
export const singleNumberAlgorithm: AlgorithmDefinition = {
id: 'single-number',
title: 'Single Number',
slug: 'single-number',
pattern: {
name: 'Bit Manipulation',
description:
'Techniques using binary operations (AND, OR, XOR, NOT, shifts) to solve problems efficiently, often achieving O(1) space where other approaches need O(n).',
},
problemStatement:
'Given a non-empty array of integers where every element appears twice except for one, find that single element. You must use O(n) time and O(1) space.',
intuition:
'XOR has two magical properties: a ^ a = 0 (any number XORed with itself is zero) and a ^ 0 = a (XORing with zero preserves the value). When you XOR all numbers together, every pair cancels out to zero, leaving only the single number.',
code: {
language: 'python',
code: `def single_number(nums: list[int]) -> int:
result = 0
for num in nums:
result ^= num # XOR each number
return result`,
},
initialExample: {
input: { nums: [2, 2, 1] },
expected: 1,
},
steps: [
// ==========================================
// Phase 1: Problem (2 steps)
// ==========================================
{
id: 'problem-1',
phase: 'problem',
explanation:
'We have an array where every element appears exactly twice, except for one element that appears only once. Our goal is to find this unique element.',
dataState: {
arrays: [
{
id: 'nums',
label: 'nums',
elements: createArrayElements(NUMS),
},
],
pointers: [],
variables: [
{ id: 'nums', name: 'nums', value: '[2, 2, 1]' },
],
calculations: [],
},
},
{
id: 'problem-2',
phase: 'problem',
explanation:
'The constraint is key: we must solve this in O(n) time with O(1) extra space. This rules out hash maps and sorting. We need a clever approach.',
dataState: {
arrays: [
{
id: 'nums',
label: 'nums',
elements: createArrayElements(NUMS),
},
],
pointers: [],
variables: [
{ id: 'time', name: 'Time', value: 'O(n)' },
{ id: 'space', name: 'Space', value: 'O(1)' },
],
calculations: [],
},
},
// ==========================================
// Phase 2: Intuition - XOR Properties (4 steps)
// ==========================================
{
id: 'intuition-1',
phase: 'intuition',
explanation:
'Let\'s explore XOR (exclusive or). At the bit level: 0⊕0=0, 1⊕1=0, 0⊕1=1, 1⊕0=1. Same bits give 0, different bits give 1.',
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'xor-rule', name: 'XOR Rule', value: 'same=0, different=1' },
],
calculations: [],
bitManipulation: [
createBitManip(
'xor-demo',
[
createBit('bit1', 0, 'comparing'),
createBit('bit2', 0, 'comparing'),
],
'XOR',
createBit('res', 0, 'result'),
'Bit XOR: 0 ⊕ 0 = 0'
),
],
},
},
{
id: 'intuition-2',
phase: 'intuition',
explanation:
'Key property #1: a ⊕ a = 0. Any number XORed with itself equals zero. All the same bits cancel out!',
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'property1', name: 'Property 1', value: 'a ⊕ a = 0' },
],
calculations: [],
bitManipulation: [
createBitManip(
'self-xor',
[
createBit('a1', 5, 'comparing', 'a'),
createBit('a2', 5, 'comparing', 'a'),
],
'XOR',
createBit('zero', 0, 'cancelled', '0'),
'5 ⊕ 5 = 0'
),
],
},
},
{
id: 'intuition-3',
phase: 'intuition',
explanation:
'Key property #2: a ⊕ 0 = a. XORing any number with zero preserves it. Zero is the identity element for XOR.',
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'property2', name: 'Property 2', value: 'a ⊕ 0 = a' },
],
calculations: [],
bitManipulation: [
createBitManip(
'zero-xor',
[
createBit('a', 5, 'highlighted', 'a'),
createBit('zero', 0, 'normal', '0'),
],
'XOR',
createBit('result', 5, 'result', 'a'),
'5 ⊕ 0 = 5'
),
],
},
},
{
id: 'intuition-4',
phase: 'intuition',
explanation:
'The insight: if we XOR all numbers together, every pair (a ⊕ a) cancels to 0, leaving only the single number (single ⊕ 0 = single)!',
dataState: {
arrays: [
{
id: 'nums',
label: 'nums',
elements: createArrayElements(NUMS),
},
],
pointers: [],
variables: [
{ id: 'insight', name: 'Insight', value: '2⊕2⊕1 = 0⊕1 = 1' },
],
calculations: [],
},
},
// ==========================================
// Phase 3: Pattern (2 steps)
// ==========================================
{
id: 'pattern-1',
phase: 'pattern',
explanation:
'The XOR accumulator pattern: start with result=0, then XOR each element. Pairs vanish, the unique element remains.',
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'pattern', name: 'Pattern', value: 'XOR Accumulator' },
{ id: 'formula', name: 'Formula', value: 'result ^= num' },
],
calculations: [],
bitManipulation: [
createBitManip(
'pattern-demo',
[createBit('result', 0, 'highlighted', 'result')],
undefined,
undefined,
'Initialize result = 0'
),
],
},
},
{
id: 'pattern-2',
phase: 'pattern',
explanation:
'This pattern works because XOR is commutative (a⊕b = b⊕a) and associative ((a⊕b)⊕c = a⊕(b⊕c)). The order doesn\'t matter!',
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'commutative', name: 'Commutative', value: 'a⊕b = b⊕a' },
{ id: 'associative', name: 'Associative', value: '(a⊕b)⊕c = a⊕(b⊕c)' },
],
calculations: [],
},
},
// ==========================================
// Phase 4: Code (2 steps)
// ==========================================
{
id: 'code-1',
phase: 'code',
explanation:
'Initialize result to 0. Since a ⊕ 0 = a, starting with 0 means the first XOR just sets result to the first number.',
codeLine: 2,
codeHighlightLines: [1, 2],
dataState: {
arrays: [
{
id: 'nums',
label: 'nums',
elements: createArrayElements(NUMS),
},
],
pointers: [],
variables: [{ id: 'result', name: 'result', value: 0 }],
calculations: [],
},
},
{
id: 'code-2',
phase: 'code',
explanation:
'Loop through each number and XOR it with result. The ^= operator is shorthand for result = result ^ num.',
codeLine: 5,
codeHighlightLines: [4, 5],
dataState: {
arrays: [
{
id: 'nums',
label: 'nums',
elements: createArrayElements(NUMS),
},
],
pointers: [],
variables: [
{ id: 'result', name: 'result', value: 0 },
{ id: 'loop', name: 'loop', value: 'for num in nums' },
],
calculations: [],
},
},
// ==========================================
// Phase 5: Execution - Process [2, 2, 1] (10 steps)
// ==========================================
{
id: 'exec-init',
phase: 'execution',
explanation:
'Initialize result = 0. In binary: 0000. We\'ll XOR each number from the array with this result.',
codeLine: 2,
dataState: {
arrays: [
{
id: 'nums',
label: 'nums',
elements: createArrayElements(NUMS),
},
],
pointers: [],
variables: [{ id: 'result', name: 'result', value: '0 (0000)' }],
calculations: [],
bitManipulation: [
createBitManip(
'init',
[createBit('result', 0, 'highlighted', 'result')],
undefined,
undefined,
'result = 0'
),
],
},
},
{
id: 'exec-1-setup',
phase: 'execution',
explanation:
'Process first element: num = 2. In binary: 0010. We\'ll compute result ⊕ num = 0 ⊕ 2.',
codeLine: 4,
dataState: {
arrays: [
{
id: 'nums',
label: 'nums',
elements: createArrayElements(NUMS, 0),
},
],
pointers: [{ id: 'i', name: 'i', index: 0, color: 'default' }],
variables: [
{ id: 'result', name: 'result', value: '0 (0000)' },
{ id: 'num', name: 'num', value: '2 (0010)' },
],
calculations: [],
bitManipulation: [
createBitManip(
'xor-1',
[
createBit('result', 0, 'comparing', 'result'),
createBit('num', 2, 'comparing', 'num'),
],
'XOR',
undefined,
'0 ⊕ 2 = ?'
),
],
},
},
{
id: 'exec-1-result',
phase: 'execution',
explanation:
'result = 0 ⊕ 2 = 2. In binary: 0000 ⊕ 0010 = 0010. Each bit: 0⊕0=0, 0⊕0=0, 0⊕1=1, 0⊕0=0.',
codeLine: 5,
dataState: {
arrays: [
{
id: 'nums',
label: 'nums',
elements: createArrayElements(NUMS, 0, []),
},
],
pointers: [{ id: 'i', name: 'i', index: 0, color: 'default' }],
variables: [
{ id: 'result', name: 'result', value: '2 (0010)', previousValue: '0' },
],
calculations: [],
bitManipulation: [
createBitManip(
'xor-1',
[
createBit('prev', 0, 'normal', 'prev'),
createBit('num', 2, 'comparing', 'num'),
],
'XOR',
createBit('result', 2, 'result', 'result'),
'0 ⊕ 2 = 2'
),
],
},
},
{
id: 'exec-2-setup',
phase: 'execution',
explanation:
'Process second element: num = 2 again. We\'ll compute result ⊕ num = 2 ⊕ 2. Same number!',
codeLine: 4,
dataState: {
arrays: [
{
id: 'nums',
label: 'nums',
elements: createArrayElements(NUMS, 1, [0]),
},
],
pointers: [{ id: 'i', name: 'i', index: 1, color: 'default' }],
variables: [
{ id: 'result', name: 'result', value: '2 (0010)' },
{ id: 'num', name: 'num', value: '2 (0010)' },
],
calculations: [],
bitManipulation: [
createBitManip(
'xor-2',
[
createBit('result', 2, 'comparing', 'result'),
createBit('num', 2, 'comparing', 'num'),
],
'XOR',
undefined,
'2 ⊕ 2 = ?'
),
],
},
},
{
id: 'exec-2-result',
phase: 'execution',
explanation:
'result = 2 ⊕ 2 = 0! The pair cancels out! In binary: 0010 ⊕ 0010 = 0000. Every bit is the same, so XOR gives all zeros.',
codeLine: 5,
dataState: {
arrays: [
{
id: 'nums',
label: 'nums',
elements: createArrayElements(NUMS, 1, [0]),
},
],
pointers: [{ id: 'i', name: 'i', index: 1, color: 'default' }],
variables: [
{ id: 'result', name: 'result', value: '0 (0000)', previousValue: '2' },
{ id: 'insight', name: 'insight', value: 'Pair cancelled!' },
],
calculations: [],
bitManipulation: [
createBitManip(
'xor-2',
[
createBit('prev', 2, 'cancelled', '2'),
createBit('num', 2, 'cancelled', '2'),
],
'XOR',
createBit('result', 0, 'cancelled', '0'),
'2 ⊕ 2 = 0 (cancelled!)'
),
],
},
},
{
id: 'exec-3-setup',
phase: 'execution',
explanation:
'Process third element: num = 1. This is the single number! We\'ll compute result ⊕ num = 0 ⊕ 1.',
codeLine: 4,
dataState: {
arrays: [
{
id: 'nums',
label: 'nums',
elements: createArrayElements(NUMS, 2, [0, 1]),
},
],
pointers: [{ id: 'i', name: 'i', index: 2, color: 'default' }],
variables: [
{ id: 'result', name: 'result', value: '0 (0000)' },
{ id: 'num', name: 'num', value: '1 (0001)' },
],
calculations: [],
bitManipulation: [
createBitManip(
'xor-3',
[
createBit('result', 0, 'comparing', 'result'),
createBit('num', 1, 'highlighted', 'num'),
],
'XOR',
undefined,
'0 ⊕ 1 = ?'
),
],
},
},
{
id: 'exec-3-result',
phase: 'execution',
explanation:
'result = 0 ⊕ 1 = 1! In binary: 0000 ⊕ 0001 = 0001. XORing with 0 preserves the value, so the single number emerges!',
codeLine: 5,
dataState: {
arrays: [
{
id: 'nums',
label: 'nums',
elements: createArrayElements(NUMS, 2, [0, 1]),
},
],
pointers: [{ id: 'i', name: 'i', index: 2, color: 'default' }],
variables: [
{ id: 'result', name: 'result', value: '1 (0001)', previousValue: '0' },
],
calculations: [],
bitManipulation: [
createBitManip(
'xor-3',
[
createBit('prev', 0, 'normal', '0'),
createBit('num', 1, 'highlighted', '1'),
],
'XOR',
createBit('result', 1, 'result', 'result'),
'0 ⊕ 1 = 1'
),
],
},
},
{
id: 'exec-done',
phase: 'execution',
explanation:
'Loop complete! Return result = 1. The pair (2, 2) cancelled out, leaving only the single number: 1.',
codeLine: 7,
codeHighlightLines: [7],
dataState: {
arrays: [
{
id: 'nums',
label: 'nums',
elements: NUMS.map((value, index) => ({
value,
index,
state: value === 1 ? 'success' : 'dimmed',
} as ArrayElementState)),
},
],
pointers: [],
variables: [
{ id: 'result', name: 'return', value: 1 },
],
calculations: [],
bitManipulation: [
createBitManip(
'final',
[createBit('result', 1, 'result', 'result')],
undefined,
undefined,
'Single Number: 1'
),
],
},
},
{
id: 'exec-summary',
phase: 'execution',
explanation:
'Summary: 0 ⊕ 2 = 2, then 2 ⊕ 2 = 0 (pair cancelled!), then 0 ⊕ 1 = 1. The single number remains after all pairs cancel.',
dataState: {
arrays: [],
pointers: [],
variables: [
{ id: 'answer', name: 'Answer', value: 1 },
{ id: 'time', name: 'Time', value: 'O(n)' },
{ id: 'space', name: 'Space', value: 'O(1)' },
],
calculations: [],
},
},
],
};