Hash Tables
First Non-Repeating Value
Find the first input value whose final frequency is one.
Algorithm
Canonical input [3, 5, 2, 5, 3, 8, 2] prints 8.
The replay uses the same input in every language, so this TypeScript DSA
implementation can be compared directly with the rest of the DSA track.
two-pass lookup
The first pass builds a frequency table. The second pass keeps the original order and stops at the first value with frequency one.
Basic Implementation
basic.ts
Replay: real traced execution (multi-file project)
const arr: number[] = [3, 5, 2, 5, 3, 8, 2];
const count = new Map<number, number>();
for (const value of arr) {
count.set(value, (count.get(value) ?? 0) + 1);
}
for (const value of arr) {
if (count.get(value) === 1) {
console.log(value);
break;
}
}
arr ← [3, 5, 2, 5, 3, 8, 2]
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();values this step[3, 5, 2, 5, 3, 8, 2]arrcount ← {}
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();3for (const value of arr) {values this step{}countcount ← {3: 1}
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();3for (const value of arr) {values this step{} → {3: 1}count3valuecount ← {3: 1, 5: 1}
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();3for (const value of arr) {values this step{3: 1} → {3: 1, 5: 1}count5valuecount ← {3: 1, 5: 1, 2: 1}
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();3for (const value of arr) {values this step{3: 1, 5: 1} → {3: 1, 5: 1, 2: 1}count2valuecount ← {3: 1, 5: 2, 2: 1}
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();3for (const value of arr) {values this step{3: 1, 5: 1, 2: 1} → {3: 1, 5: 2, 2: 1}count5valuecount ← {3: 2, 5: 2, 2: 1}
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();3for (const value of arr) {values this step{3: 1, 5: 2, 2: 1} → {3: 2, 5: 2, 2: 1}count3valuecount ← {3: 2, 5: 2, 2: 1, 8: 1}
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();3for (const value of arr) {values this step{3: 2, 5: 2, 2: 1} → {3: 2, 5: 2, 2: 1, 8: 1}count8valuecount ← {3: 2, 5: 2, 2: 2, 8: 1}
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();3for (const value of arr) {values this step{3: 2, 5: 2, 2: 1, 8: 1} → {3: 2, 5: 2, 2: 2, 8: 1}count2valuei ← 0, value ← 3, count[value] ← 2, found ← no
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();values this step0i3value2count[value]nofound{3: 2, 5: 2, 2: 2, 8: 1}counti ← 1, value ← 5, count[value] ← 2, found ← no
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();values this step1i5value2count[value]nofound{3: 2, 5: 2, 2: 2, 8: 1}counti ← 2, value ← 2, count[value] ← 2, found ← no
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();values this step2i2value2count[value]nofound{3: 2, 5: 2, 2: 2, 8: 1}counti ← 3, value ← 5, count[value] ← 2, found ← no
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();values this step3i5value2count[value]nofound{3: 2, 5: 2, 2: 2, 8: 1}counti ← 4, value ← 3, count[value] ← 2, found ← no
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();values this step4i3value2count[value]nofound{3: 2, 5: 2, 2: 2, 8: 1}counti ← 5, value ← 8, count[value] ← 1, found ← yes, result ← 8
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();values this step5i8value1count[value]yesfound8result{3: 2, 5: 2, 2: 2, 8: 1}countstdout ← 8
1const arr: number[] = [3, 5, 2, 5, 3, 8, 2];2const count = new Map<number, number>();values this step8stdout8result
Complexity
- Time: O(n) average
- Space: O(k) for k distinct values
Implementation notes
- TypeScript declares the input as
const arr: number[]; this checked lesson iterates numeric values, not string characters. - Counts are stored in
new Map<number, number>(), so both keys and frequency values are typed asnumber. - The first pass uses
for (const value of arr)and writescount.set(value, (count.get(value) ?? 0) + 1). The nullish coalescing default handles theundefinedresult for a value not seen before. - The second pass scans the same array order and checks whether
count.get(value)is1. Every scanned value was inserted during the first pass, so the replay does not observe an undefined count lookup in this phase. - The trace grows the table from
{}to{3: 1},{3: 1, 5: 1},{3: 1, 5: 1, 2: 1}, and finally{3: 2, 5: 2, 2: 2, 8: 1}. It rejects counts of2until value8has count1. console.log(value)prints8and breaks. Visible allocation is the input array and theMap; mutation is limited tocount.set.