single number algorithm def
This commit is contained in:
628
frontend/src/content/algorithms/single-number.ts
Normal file
628
frontend/src/content/algorithms/single-number.ts
Normal 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: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user