Skip to content

Commit 8cd126d

Browse files
committedOct 28, 2020
feat(book/linkedlist): linked lists techniques and common patterns
1 parent 0f13f90 commit 8cd126d

30 files changed

+945
-351
lines changed
 

Diff for: ‎.eslintrc.js

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,35 @@
11
module.exports = {
22
extends: 'airbnb-base',
33
env: {
4-
jest: true
4+
jest: true,
55
},
6+
plugins: ['jest'],
67
globals: {
78
BigInt: true,
89
},
10+
11+
// check package.json for files to include
12+
// files: ['src/**/*.js', 'book/interview-questions/*.js'],
13+
914
rules: {
1015
// https://github.com/airbnb/javascript/issues/1089
1116

1217
// https://stackoverflow.com/a/35637900/684957
1318
// allow to add properties to arguments
14-
'no-param-reassign': [2, { 'props': false }],
19+
'no-param-reassign': [2, { props: false }],
1520

1621
// https://eslint.org/docs/rules/no-plusplus
1722
// allows unary operators ++ and -- in the afterthought (final expression) of a for loop.
18-
'no-plusplus': [0, { 'allowForLoopAfterthoughts': true }],
23+
'no-plusplus': [0, { allowForLoopAfterthoughts: true }],
1924
'no-continue': [0],
2025

2126
// Allow for..of
2227
'no-restricted-syntax': [0, 'ForOfStatement'],
23-
}
28+
29+
// jest plugin
30+
// 'jest/no-disabled-tests': 'warn',
31+
'jest/no-focused-tests': 'error',
32+
'jest/no-identical-title': 'warn',
33+
'jest/valid-expect': 'warn',
34+
},
2435
};

Diff for: ‎book/content/part02/linked-list.asc

+291-12
Large diffs are not rendered by default.

Diff for: ‎book/images/Find-the-largest-sum.png

-418 Bytes
Loading
1.18 KB
Loading

Diff for: ‎book/images/Words-Permutations.png

4.2 KB
Loading

Diff for: ‎book/images/cll-fast-slow-pointers.png

137 KB
Loading

Diff for: ‎book/images/cll.png

70.5 KB
Loading

Diff for: ‎book/images/course-schedule-examples.png

-3.69 KB
Loading

Diff for: ‎book/images/critical-connections-sol-examples.png

-1.42 KB
Loading

Diff for: ‎book/images/critical-path-examples.png

-1.38 KB
Loading

Diff for: ‎book/images/mll-3-levels.png

30.3 KB
Loading

Diff for: ‎book/images/sll-fast-slow-pointers.png

23.5 KB
Loading

Diff for: ‎book/images/sllx4.png

5.28 KB
Loading

Diff for: ‎book/interview-questions/daily-temperatures.spec.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable max-len */
12
const { dailyTemperatures } = require('./daily-temperatures');
23

34
describe('Stack: Daily Temperatures', () => {
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// tag::fn[]
2+
/**
3+
* Find where the cycle starts or null if no loop.
4+
* @param {Node} head - The head of the list
5+
* @returns {Node|null}
6+
*/
7+
function findCycleStart(head) {
8+
let slow = head;
9+
let fast = head;
10+
while (fast && fast.next) {
11+
slow = slow.next; // slow moves 1 by 1.
12+
fast = fast.next.next; // slow moves 2 by 2.
13+
if (fast === slow) { // detects loop!
14+
slow = head; // reset pointer to begining.
15+
while (slow !== fast) { // find intersection
16+
slow = slow.next;
17+
fast = fast.next; // move both pointers one by one this time.
18+
}
19+
return slow; // return where the loop starts
20+
}
21+
}
22+
return null; // not found.
23+
}
24+
// end::fn[]
25+
26+
// tag::brute[]
27+
function findCycleStartBrute(head) {
28+
const visited = new Set();
29+
let curr = head;
30+
while (curr) {
31+
if (visited.has(curr)) return curr;
32+
visited.add(curr);
33+
curr = curr.next;
34+
}
35+
return null;
36+
}
37+
// end::brute[]
38+
39+
module.exports = { findCycleStart, findCycleStartBrute };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const { findCycleStart, findCycleStartBrute } = require('./linkedlist-find-cycle-start');
2+
const { LinkedList } = require('../../src/index');
3+
4+
[findCycleStart, findCycleStartBrute].forEach((fn) => {
5+
describe(`findCycleStart: ${fn.name}`, () => {
6+
it('should work without loop', () => {
7+
const head = new LinkedList([1, 2, 3]).first;
8+
expect(fn(head)).toEqual(null);
9+
});
10+
11+
it('should work with loop on first', () => {
12+
const list = new LinkedList([1, 2, 3]);
13+
const n1 = list.first;
14+
list.last.next = n1;
15+
expect(fn(list.first)).toEqual(n1);
16+
});
17+
18+
it('should work with loop on second', () => {
19+
const list = new LinkedList([1, 2, 3]);
20+
const n2 = list.first.next;
21+
list.last.next = n2;
22+
expect(fn(list.first)).toEqual(n2);
23+
});
24+
});
25+
});
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// tag::fn[]
2+
/**
3+
* Flatten a multi-level to a single level
4+
* @param {Node} head
5+
* @return {Node}
6+
*/
7+
function flatten(head) {
8+
for (let curr = head; curr; curr = curr.next) {
9+
if (!curr.child) continue;
10+
11+
let last = curr.child;
12+
while (last && last.next) last = last.next; // find "child"'s last
13+
if (curr.next) { // move "next" to "child"'s last postion
14+
last.next = curr.next;
15+
curr.next.previous = last;
16+
}
17+
curr.next = curr.child; // override "next" with "child".
18+
curr.child.previous = curr;
19+
curr.child = null; // clean "child" pointer.
20+
}
21+
22+
return head;
23+
}
24+
// end::fn[]
25+
26+
// tag::fn2[]
27+
function flattenBrute(head) {
28+
const stack = [];
29+
for (let curr = head; curr; curr = curr.next) {
30+
if (!curr.next && stack.length) {
31+
curr.next = stack.pop(); // merge main thread with saved nodes.
32+
curr.next.previous = curr;
33+
}
34+
if (!curr.child) continue;
35+
if (curr.next) stack.push(curr.next); // save "next" nodes.
36+
curr.next = curr.child; // override next pointer with "child"
37+
curr.child.previous = curr;
38+
curr.child = null; // clear child pointer (was moved to "next").
39+
}
40+
41+
return head;
42+
}
43+
// end::fn2[]
44+
45+
module.exports = { flatten, flattenBrute };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/* eslint-disable one-var, one-var-declaration-per-line, prefer-destructuring */
2+
const { flatten, flattenBrute } = require('./linkedlist-flatten-multilevel');
3+
const { LinkedList } = require('../../src/index');
4+
const { ListNode } = require('../../src/index');
5+
6+
class Node extends ListNode {
7+
constructor(value) {
8+
super(value);
9+
this.child = null;
10+
}
11+
}
12+
13+
// print linked list node with (previous and child)
14+
const toString = (head) => {
15+
const arr = [];
16+
for (let i = head; i; i = i.next) {
17+
arr.push(`${i.value}(${(i.previous && i.previous.value) || ''},${(i.child && i.child.value) || ''})`);
18+
}
19+
return `{ ${arr.join(' -> ')} }`;
20+
};
21+
22+
const ll = (nums) => Array.from(new LinkedList(nums, Node));
23+
24+
[flatten, flattenBrute].forEach((fn) => {
25+
describe(`flatten: ${fn.name}`, () => {
26+
let l1, l2, l3, l4;
27+
28+
beforeEach(() => {
29+
l1 = ll([1, 2, 3]);
30+
l2 = ll([10, 12, 14, 16]);
31+
l3 = ll([21, 23]);
32+
l4 = ll([36, 37]);
33+
});
34+
35+
it('works with flat 1 level', () => {
36+
// 1--- 2--- 3
37+
expect(toString(fn(l1[0]))).toEqual('{ 1(,) -> 2(1,) -> 3(2,) }');
38+
});
39+
40+
it('works with flat 2 levels', () => {
41+
// 21--23
42+
// |
43+
// 36--37
44+
l3[1].child = l4[0];
45+
expect(toString(l3[0])).toEqual('{ 21(,) -> 23(21,36) }');
46+
expect(toString(fn(l3[0]))).toEqual('{ 21(,) -> 23(21,) -> 36(23,) -> 37(36,) }');
47+
});
48+
49+
fit('works with flat 2 levels and reminder', () => {
50+
// 1--- 2--- 3
51+
// |
52+
// 36--37
53+
l1[1].child = l4[0];
54+
expect(toString(l1[0])).toEqual('{ 1(,) -> 2(1,36) -> 3(2,) }');
55+
56+
expect(toString(fn(l1[0]))).toEqual('{ 1(,) -> 2(1,) -> 36(2,) -> 37(36,) -> 3(37,) }');
57+
});
58+
59+
it('should flatten 3 levels', () => {
60+
// 1--- 2--- 3
61+
// |
62+
// 10---12---14---16
63+
// | |
64+
// | 36---37
65+
// |
66+
// 21--23
67+
l1[1].child = l2[0];
68+
l2[1].child = l3[0];
69+
l2[2].child = l4[0];
70+
71+
// verify list children are present
72+
expect(toString(l1[0])).toEqual('{ 1(,) -> 2(1,10) -> 3(2,) }');
73+
expect(toString(l2[0])).toEqual('{ 10(,) -> 12(10,21) -> 14(12,36) -> 16(14,) }');
74+
75+
// run
76+
expect(toString(fn(l1[0]))).toEqual('{ 1(,) -> 2(1,) -> 10(2,) -> 12(10,) -> 21(12,) -> 23(21,) -> 14(23,) -> 36(14,) -> 37(36,) -> 16(37,) -> 3(16,) }');
77+
});
78+
});
79+
});

Diff for: ‎book/interview-questions/linkedlist-is-palindrome.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// tag::fn[]
2+
function isPalindrome(head) {
3+
let slow = head;
4+
let fast = head;
5+
while (fast) { // use slow/fast pointers to find the middle.
6+
slow = slow.next;
7+
fast = fast.next && fast.next.next;
8+
}
9+
10+
const reverseList = (node) => { // use 3 pointers to reverse a linked list
11+
let prev = null;
12+
let curr = node;
13+
while (curr) {
14+
const { next } = curr; // same as: "const next = curr.next;"
15+
curr.next = prev;
16+
prev = curr;
17+
curr = next;
18+
}
19+
return prev;
20+
};
21+
22+
const reversed = reverseList(slow); // head of the reversed half
23+
for (let i = reversed, j = head; i; i = i.next, j = j.next) if (i.value !== j.value) return false;
24+
return true;
25+
}
26+
// end::fn[]
27+
28+
// tag::fn2[]
29+
function isPalindromeBrute(head) {
30+
const arr = [];
31+
for (let i = head; i; i = i.next) arr.push(i.value); // <1>
32+
let lo = 0;
33+
let hi = arr.length - 1;
34+
while (lo < hi) if (arr[lo++] !== arr[hi--]) return false; // <2>
35+
return true;
36+
}
37+
// end::fn2[]
38+
39+
module.exports = { isPalindrome, isPalindromeBrute };
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const { isPalindrome, isPalindromeBrute } = require('./linkedlist-is-palindrome');
2+
const { LinkedList } = require('../../src');
3+
4+
const toList = (arr) => new LinkedList(arr).first;
5+
6+
[isPalindrome, isPalindromeBrute].forEach((fn) => {
7+
describe(`isPalindrome: ${fn.name}`, () => {
8+
it('should work', () => {
9+
expect(fn()).toEqual(true);
10+
});
11+
12+
it('should work different cases', () => {
13+
expect(fn(toList([1, 2, 3]))).toEqual(false);
14+
expect(fn(toList([1, 2, 3, 2, 1]))).toEqual(true);
15+
expect(fn(toList([1, 1, 2, 1]))).toEqual(false);
16+
expect(fn(toList([1, 2, 2, 1]))).toEqual(true);
17+
});
18+
});
19+
});

Diff for: ‎book/interview-questions/max-subarray.data.js

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: ‎book/interview-questions/network-delay-time.spec.js

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: ‎book/interview-questions/recent-counter.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ const { Queue } = require('../../src/index');
2020
class RecentCounter {
2121
// end::description[]
2222
// tag::solution[]
23-
queue = new Queue();
2423
// end::solution[]
2524
// tag::description[]
2625
/**
@@ -31,6 +30,7 @@ class RecentCounter {
3130
// end::description[]
3231
// tag::solution[]
3332
this.window = maxWindow;
33+
this.queue = new Queue();
3434
// end::solution[]
3535
// tag::description[]
3636
}
@@ -44,8 +44,7 @@ class RecentCounter {
4444
// end::description[]
4545
// tag::solution[]
4646
this.queue.enqueue(timestamp);
47-
while (timestamp - this.queue.peek() > this.window)
48-
this.queue.dequeue();
47+
while (timestamp - this.queue.peek() > this.window) this.queue.dequeue();
4948

5049
return this.queue.size;
5150
// end::solution[]

Diff for: ‎book/interview-questions/sort-colors.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable no-return-assign */
12
// const { } = require('../../src/index');
23

34
// tag::description[]
@@ -35,7 +36,8 @@ function sortColors(nums) {
3536

3637
// tag::compact[]
3738
function sortColorsCompact(nums) {
38-
let i = 0, lo = 0, hi = nums.length - 1;
39+
let i = 0; let lo = 0; let
40+
hi = nums.length - 1;
3941
const swap = (k, j) => [nums[k], nums[j]] = [nums[j], nums[k]];
4042

4143
while (i <= hi) {

Diff for: ‎package-lock.json

+335-312
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: ‎package.json

+5-4
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
"scripts": {
1616
"test": "jest --verbose",
1717
"watch": "jest --watch --coverage",
18-
"ci": "npx eslint src/ && jest --coverage",
18+
"lint:base": "npx eslint --fix '{src,book/interview-questions}/**/*.js'",
19+
"lint": "npm run lint:base -- --format codeframe",
20+
"ci": "npm run lint:base && jest --coverage",
1921
"coverage": "jest --coverage && open coverage/lcov-report/index.html",
2022
"coverage:win": "jest --coverage && cmd.exe /C start coverage/lcov-report/index.html",
21-
"lint": "npx eslint --fix --format codeframe src/",
2223
"semantic-release": "semantic-release",
2324
"release:check": "semantic-release --dry-run"
2425
},
@@ -40,10 +41,10 @@
4041
"commitizen": "4.1.2",
4142
"conventional-changelog-cli": "2.0.34",
4243
"cz-conventional-changelog": "3.2.0",
43-
"eslint": "7.0.0",
44+
"eslint": "7.12.1",
4445
"eslint-config-airbnb-base": "14.1.0",
4546
"eslint-plugin-import": "2.20.2",
46-
"eslint-plugin-jest": "23.11.0",
47+
"eslint-plugin-jest": "24.1.0",
4748
"handlebars": "4.7.6",
4849
"husky": "4.2.5",
4950
"jest": "26.0.1",

Diff for: ‎src/data-structures/linked-lists/linked-list.js

+15-9
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
const util = require('util');
2-
const Node = require('./node');
2+
const Node = require('./node'); // Doubly
33

44
// tag::constructor[]
55
/**
66
* Doubly linked list that keeps track of
77
* the last and first element
88
*/
99
class LinkedList {
10-
constructor(iterable = []) {
10+
constructor(
11+
iterable = [],
12+
// end::constructor[]
13+
ListNode = Node, // Node class (e.g. singly, doubly, multilevel)
14+
// tag::constructor[]
15+
) {
1116
this.first = null; // head/root element
1217
this.last = null; // last element of the list
1318
this.size = 0; // total number of elements in the list
19+
// end::constructor[]
20+
this.ListNode = ListNode; // ListNode class
21+
// tag::constructor[]
1422

1523
Array.from(iterable, (i) => this.addLast(i));
1624
}
@@ -20,10 +28,10 @@ class LinkedList {
2028
/**
2129
* Adds element to the begining of the list. Similar to Array.unshift
2230
* Runtime: O(1)
23-
* @param {any} value
31+
* @param {Node} value
2432
*/
2533
addFirst(value) {
26-
const newNode = new Node(value);
34+
const newNode = new this.ListNode(value);
2735

2836
newNode.next = this.first;
2937

@@ -246,16 +254,14 @@ class LinkedList {
246254
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators#User-defined_iterables
247255
*/
248256
* [Symbol.iterator]() {
249-
for (let node = this.first, position = 0;
250-
node;
251-
position += 1, node = node.next) {
252-
yield { node, position };
257+
for (let node = this.first; node; node = node.next) {
258+
yield node;
253259
}
254260
}
255261

256262
toString() {
257263
const parts = [...this]; // see [Symbol.iterator]()
258-
return parts.map((n) => util.inspect(n.node.value)).join(' -> ');
264+
return parts.map((n) => util.inspect(n.value)).join(' -> ');
259265
}
260266

261267
/**

Diff for: ‎src/data-structures/linked-lists/linked-list.spec.js

+19
Original file line numberDiff line numberDiff line change
@@ -429,5 +429,24 @@ describe('LinkedList Test', () => {
429429
expect(linkedList.toString()).toBe("'a' -> 2 -> 'c' -> { k: 4, v: 'd' }");
430430
});
431431
});
432+
433+
434+
describe('iterator', () => {
435+
let a;
436+
let b;
437+
let c;
438+
let d;
439+
beforeEach(() => {
440+
a = linkedList.addLast('a');
441+
b = linkedList.addLast('b');
442+
c = linkedList.addLast('c');
443+
d = linkedList.addLast('d');
444+
});
445+
446+
it('should convert to array of nodes', () => {
447+
expect([...linkedList]).toEqual([a, b, c, d]);
448+
expect(Array.from(linkedList)).toEqual([a, b, c, d]);
449+
});
450+
});
432451
});
433452
});

Diff for: ‎src/data-structures/linked-lists/node.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
// tag::snippet[]
22
/**
3-
* Node with reference to next and previous element
3+
* Linked List Node
44
*/
5+
// tag::singly[]
56
class Node {
67
constructor(value = null) {
78
this.value = value;
89
this.next = null;
9-
this.previous = null; // for doubly linked list
10+
// end::singly[]
11+
this.previous = null; // if doubly linked list
12+
// tag::singly[]
1013
}
1114
}
15+
// end::singly[]
1216
// end::snippet[]
1317

1418
module.exports = Node;

Diff for: ‎src/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// data structures
22
const LinkedList = require('./data-structures/linked-lists/linked-list');
3+
const ListNode = require('./data-structures/linked-lists/node');
34
const Queue = require('./data-structures/queues/queue');
45
const Stack = require('./data-structures/stacks/stack');
56
const Graph = require('./data-structures/graphs/graph');
@@ -29,6 +30,7 @@ const mergeSort = require('./algorithms/sorting/merge-sort');
2930

3031
module.exports = {
3132
LinkedList,
33+
ListNode,
3234
Queue,
3335
Stack,
3436
Graph,

0 commit comments

Comments
 (0)
Please sign in to comment.