Skip to content

Commit 2512481

Browse files
committed
feat: implement leetcode 0001
1 parent baf8f9e commit 2512481

File tree

7 files changed

+251
-0
lines changed

7 files changed

+251
-0
lines changed

src/leet-code/0001-two-sum/README.md

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Two Sum
2+
3+
Given an array of integers `nums` and an integer `target`, return the two
4+
indexes of the numbers that add up to `target`.
5+
6+
* Assume that `nums` contains at most one solution.
7+
* Can't use the same element twice.
8+
* Answer must be in ascending order.
9+
* Throw an error if `nums` has no solution for `target`.
10+
11+
## Function Signature
12+
13+
```typescript
14+
function twoSum(nums: number[], target: number): [number, number]
15+
```
16+
17+
## Expected Behavior
18+
19+
```
20+
In: [3, 2], 5
21+
Out: [0, 1]
22+
23+
In: [4, -7, 9, 2], 11
24+
Out: [2, 3]
25+
26+
In: [4, -7, 9, 2, -5], -12
27+
Out: [1, 4]
28+
29+
In: [3, 2], -10
30+
Out: Throw error
31+
32+
In: [9], 9
33+
Out: Throw error
34+
35+
In: [], 0
36+
Out: Throw error
37+
```
38+
39+
## Optimization
40+
41+
Consider:
42+
43+
```
44+
nums[x] + nums[y] = target
45+
target - nums[x] = nums[y]
46+
```
47+
48+
Two nested iterations are needed to find all `nums[x] + nums[y]`. This results
49+
in cuadratic time complexity `O(n^2)`.
50+
51+
In one single iteration we can calculate `target - nums[x]` for all `x` and
52+
perform a fast lookup (like the ones you can perform using a hash map) to
53+
check if we have already seen one of the results.
54+
55+
This avoids nested iterations, resulting in linear time complexity `O(n)`. By
56+
having the results as map keys, the `has()` and `get()` operations cost linear
57+
time.
58+
59+
The trade-off is that we need a place to store the results, so space complexity
60+
goes from constant `O(1)` to linear `O(n)`.
61+
62+
## Complexity Analysis
63+
64+
| solution | time | space |
65+
| ------------- | ------ | ----- |
66+
| nested loops | O(n^2) | O(1) |
67+
| hash map | O(n) | O(n) |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Finds the two indexes of the two numbers that add up to 'target'. Assumes
3+
* that there is exactly one solution. Uses a hash map to iterate only once, and
4+
* one for loop.
5+
*
6+
* Time complexity: O(n)
7+
* Space complexity: O(n) (space used by the hash map)
8+
*
9+
* @throws {Error} Array must contain a solution.
10+
* @param {number[]} nums Numeric array to be searched.
11+
* @param {number} target Target sum to search for.
12+
* @returns {[number, number]} Indexes of the two numbers that add up to
13+
* 'target'.
14+
*/
15+
const twoSum = (nums, target) => {
16+
/** @type {Map<number, number>} */
17+
const difIdxMap = new Map()
18+
19+
for (let idx = 0; idx < nums.length; idx++) {
20+
const dif = target - nums[idx]
21+
if (difIdxMap.has(dif)) return [difIdxMap.get(dif) ?? -1, idx]
22+
else difIdxMap.set(nums[idx], idx)
23+
}
24+
25+
throw new Error(`Array [${nums}] has no solution for ${target}`)
26+
}
27+
28+
module.exports = {
29+
fun: twoSum,
30+
id: 'hash-map for-loop'
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Finds the two indexes of the two numbers that add up to 'target'. Assumes
3+
* that there is exactly one solution. Uses a hash map to iterate only once,
4+
* and tail recursion.
5+
*
6+
* Time complexity: O(n)
7+
* Space complexity: O(n) (space used by the hash map)
8+
*
9+
* @throws {Error} Array must contain a solution.
10+
* @param {number[]} nums Numeric array to be searched.
11+
* @param {number} target Target sum to search for.
12+
* @returns {[number, number]} Indexes of the two numbers that add up to
13+
* 'target'.
14+
*/
15+
const twoSum = (nums, target) => {
16+
/**
17+
* Helper recursive function.
18+
*
19+
* @param {number} idx Current index.
20+
* @param {Map<number, number>} difIdxMap Map of differences - indexes.
21+
* @returns {[number, number]}
22+
*/
23+
const twoSumRecur = (idx, difIdxMap) => {
24+
if (idx >= nums.length) {
25+
throw new Error(`Array [${nums}] has no solution for ${target}`)
26+
}
27+
28+
const dif = target - nums[idx]
29+
if (difIdxMap.has(dif)) {
30+
return [difIdxMap.get(dif) ?? -1, idx]
31+
}
32+
33+
return twoSumRecur(idx + 1, difIdxMap.set(nums[idx], idx))
34+
}
35+
36+
return twoSumRecur(0, new Map())
37+
}
38+
39+
module.exports = {
40+
fun: twoSum,
41+
id: 'hash-map tail-recursion'
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Finds the two indexes of the two numbers that add up to 'target'. Assumes
3+
* that there is exactly one solution. Uses a naive brute-force approach.
4+
*
5+
* Time complexity: O(n^2)
6+
* Space complexity: O(1)
7+
*
8+
* @throws {Error} Array must contain a solution.
9+
* @param {number[]} nums Numeric array to be searched.
10+
* @param {number} target Target sum to search for.
11+
* @returns {[number, number]} Indexes of the two numbers that add up to
12+
* 'target'.
13+
*/
14+
const twoSum = (nums, target) => {
15+
for (let pivotIdx = 0; pivotIdx < nums.length - 1; pivotIdx++) {
16+
for (let idx = pivotIdx + 1; idx < nums.length; idx++) {
17+
if (nums[pivotIdx] + nums[idx] === target) {
18+
return [pivotIdx, idx]
19+
}
20+
}
21+
}
22+
23+
throw new Error(`Array [${nums}] has no solution for ${target}`)
24+
}
25+
26+
module.exports = {
27+
fun: twoSum,
28+
id: 'naive'
29+
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const twoSumNaive = require('./two-sum-naive')
2+
const twoSumMapFor = require('./two-sum-map-for')
3+
const twoSumMapTail = require('./two-sum-map-tail')
4+
5+
const solutions = [
6+
twoSumNaive,
7+
twoSumMapFor,
8+
twoSumMapTail
9+
]
10+
11+
module.exports = solutions
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const assert = require('node:assert/strict')
2+
const { describe, it } = require('node:test')
3+
4+
const solutions = require('./two-sum.repo')
5+
6+
for (const { fun, id } of solutions) {
7+
describe(`Leetcode 0001 'Two Sum' solution ${id}`, () => {
8+
it('Solves array of length 2', () => {
9+
assert.deepEqual(
10+
fun([3, 2], 5),
11+
[0, 1]
12+
)
13+
})
14+
15+
it('Solves array of lengh 4', () => {
16+
assert.deepEqual(
17+
fun([4, -7, 9, 2], 11),
18+
[2, 3]
19+
)
20+
})
21+
22+
it('Solves array of lengh 5', () => {
23+
assert.deepEqual(
24+
fun([4, -7, 9, 2, -5], -12),
25+
[1, 4]
26+
)
27+
})
28+
29+
it('Solves for sume of the same values', () => {
30+
assert.deepEqual(
31+
fun([4, 8, -7, 8, 3], 16),
32+
[1, 3]
33+
)
34+
})
35+
36+
it('Throws error if no solution is found in array of length 5', () => {
37+
assert.throws(() => fun([3, 2, 1, 0, -1], -10), Error)
38+
})
39+
40+
it('Throws error if no solution is found in array of length 2', () => {
41+
assert.throws(() => fun([3, 2], 9), Error)
42+
})
43+
44+
it('Throws error if no solution is found in array of length 1', () => {
45+
assert.throws(() => fun([3], -6), Error)
46+
})
47+
48+
it('Throws error if no solution is found in array of length 0', () => {
49+
assert.throws(() => fun([], 7), Error)
50+
})
51+
})
52+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const { timeAndReport } = require('../../../lib/time')
2+
3+
const solutions = require('./two-sum.repo')
4+
5+
const nums = [
6+
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
7+
8, 7, 6, 5, 4, 3, 2, 1, 0, 2,
8+
4, 6, 8, 1, 3, 5, 7, 9, 3, 6,
9+
9, 4, 8, 9, 8, 6, 5, 3, 2, 1,
10+
8, 6, 4, 2, 5, 1, 9, 5, 3, 8,
11+
0, 4, 0, 8, 0, 6, 0, 4, 0, 3,
12+
2, -9, 4, 5, 7, 8, -8, 7, 4, 6
13+
]
14+
15+
const args = [nums, -17]
16+
const runs = 10_000
17+
const id = 'LeetCode 0001 - Two Sum'
18+
19+
timeAndReport(solutions, args, runs, id)

0 commit comments

Comments
 (0)