Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A simple and clearer solution for 1.7 - Rotate Matrix #105

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
22 changes: 22 additions & 0 deletions chapter01/1.7 - Rotate Matrix/rotateMatrix2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
function rotateMatrix(matrix) {
// Create a new matrix with the same dimensions as the original
const n = matrix.length;
const rotatedMatrix = new Array(n).fill(0).map(() => new Array(n).fill(0));

// Iterate through the original matrix's rows
for (let i = 0; i < matrix.length; i++) {
// Iterate through the original matrix's columns
for (let j = 0; j < matrix[i].length; j++) {
// Rotate the matrix by placing the original matrix's column in the new matrix's row
rotatedMatrix[j][matrix.length - 1 - i] = matrix[i][j];
}
}
return rotatedMatrix;
}

const testMatrix = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]];
console.log('initial matrix:')
console.log(testMatrix);
const rotatedMatrix = rotateMatrix(testMatrix);
console.log('rotated matrix:')
console.log(rotatedMatrix);
49 changes: 49 additions & 0 deletions chapter02/2.5 - Sum Lists/forward2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const LinkedList = require('../util/LinkedList')
const printList = require('../util/printList')

function Node(data) {
this.data = data
this.next = null
}


function addLists(l1, l2) {
let dummy = new Node(0);
let current = dummy; // 1
let carry = 0;

while (l1 || l2) {
let x = l1 ? l1.data : 0;
let y = l2 ? l2.data : 0;
let sum = x + y + carry;
carry = Math.floor(sum / 10); // 0
current.next = new Node(sum % 10); //7
current = current.next;
if (l1) l1 = l1.next;
if (l2) l2 = l2.next;
}

if (carry > 0) {
current.next = new Node(carry);
}

return dummy.next;
}

// First linked list (representing the number 543)
let l1 = new Node(3);
l1.next = new Node(4);

// Second linked list (representing the number 678)
let l2 = new Node(8);
l2.next = new Node(2);

// Add the two linked lists
let sum = addLists(l1, l2);

// Print the sum
let current = sum;
while (current) {
console.log(current.data);
current = current.next;
}
1 change: 1 addition & 0 deletions chapter02/2.5 - Sum Lists/sumLists - forward.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const LinkedList = require('./../util/LinkedList')
const printList = require('./../util/printList')


function sumLinkedListsForward(list1, list2) {
if (!list1 && !list2) {
return null
Expand Down
42 changes: 42 additions & 0 deletions chapter04/4.01 - Route Between Nodes/routeBetweenNodes2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const Graph = require('./../util/Graph');

Graph.prototype.hasRoute = function(startNode, endNode) {
if (!this.hasNode(startNode) || !this.hasNode(endNode)) {
return 'One or both of the nodes do not exist in the graph';
}
let queue = [startNode];
let visited = new Set();
while (queue.length > 0) {
let currentNode = queue.shift();
visited.add(currentNode);
for (let edge in this.findEdges(currentNode)) {
if (edge === endNode) {
return true;
}
if (!visited.has(edge)) {
queue.push(edge);
}
}
}
return false;
};

/* TEST */
const graph = new Graph();
graph.addNode('A');
graph.addNode('B');
graph.addNode('C');
graph.addNode('D');
graph.addNode('E');

graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('B', 'C');

graph.addEdge('D', 'E');


console.log(graph.hasRoute('A', 'C', graph), true);
console.log(graph.hasRoute('A', 'E', graph), false);
console.log(graph.hasRoute('B', 'A', graph), true);
console.log(graph.hasRoute('D', 'E', graph), true);
37 changes: 37 additions & 0 deletions chapter04/4.02 - Minimal Tree/minimalTree2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const BST = require('./../util/BST');

const createMinimalBST = (arr) => {
return createMinimalBSTHelper(arr, 0, arr.length - 1);
};

const createMinimalBSTHelper = (arr, start, end) => {
if (end < start) {
return null;
}
let mid = Math.floor((start + end) / 2);
let bst = new BST(arr[mid]);
bst.left = createMinimalBSTHelper(arr, start, mid - 1);
bst.right = createMinimalBSTHelper(arr, mid + 1, end);
return bst;
};

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let root = createMinimalBST(arr);
root.printLevelOrder();

const arr2 = [1, 2, 3, 4, 5, 6, 7];
root = createMinimalBST(arr2);
root.printLevelOrder();

// This algorithm uses a divide and conquer approach to create the binary search tree,
//similar to the previous example. It starts by finding the middle element in the array
//and creating a new BST object with that value. Then it recursively creates the left and
// right subtrees by calling the same function on the left and right halves of the original array.

// The key idea is that by choosing the middle element of the array as the root, we ensure that the tree
//is balanced and the height is minimal. Because the array is sorted and it's unique, the left and right
//subtrees will also be balanced and the tree will be a minimal height binary search tree.

// This algorithm has a time complexity of O(n) and a space complexity of O(n) where n is the number of elements
//in the array because it uses a recursive call for each element of the array and constructs a new BST object for
// each element.
71 changes: 71 additions & 0 deletions chapter04/4.03 - List of Depths/listOfDepths2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
class Node {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}

class LinkedList {
constructor() {
this.head = null;
this.tail = null;
}

addNode(node) {
if (this.tail) {
this.tail.next = node;
this.tail = node;
} else {
this.head = node;
this.tail = node;
}
}
}

const createLinkedLists = (root) => {
let lists = [];
createLinkedListsHelper(root, lists, 0);
return lists;
};

const createLinkedListsHelper = (root, lists, level) => {
if (!root) return;
let list;
if (lists.length === level) {
list = new LinkedList();
lists.push(list);
} else {
list = lists[level];
}
list.addNode(root);
createLinkedListsHelper(root.left, lists, level + 1);
createLinkedListsHelper(root.right, lists, level + 1);
};

let root = new Node(1);
root.left = new Node(2);
root.right = new Node(3);
root.left.left = new Node(4);
root.left.right = new Node(5);
root.right.left = new Node(6);
root.right.right = new Node(7);

console.log(createLinkedLists(root));

/*

This algorithm uses a depth-first traversal approach, it creates a new linked list for each level of the tree and
uses recursion to traverse through the tree. It starts by initializing an empty array called lists that will hold
the linked lists for each level of the tree. Then, it calls the helper function createLinkedListsHelper to traverse
through the tree and create the linked lists.

In the helper function, first it checks if the current node is null, if so it returns. Then, it checks if the current
level of the tree is already in the lists array. If it is not, it creates a new linked list, otherwise, it gets the
current linked list. Then it adds the current node to the current linked list. Finally, it recursively calls the
helper function on the left and right children of the current node, and increments the level by 1.

The time complexity of this algorithm is O(n) where n is the number of nodes in the tree, and space complexity is O(d)
where d is the depth of the tree, because it creates a linked list for each level of the tree.

*/
37 changes: 37 additions & 0 deletions chapter04/4.07 - Build Order/buildOrder2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const buildOrder2 = (projects, dependencies) => {
let graph = {};
let totalDegrees = {};
let queue = [];
let order = [];

//create graph and totalDegrees
for (let project of projects) {
graph[project] = [];
totalDegrees[project] = 0;
}

//add edges and degree count
for(let [from, to] of dependencies){
graph[from].push(to)
totalDegrees[to]++;
}

//add nodes with 0 degrees to the queue
for(let project in totalDegrees)
if (totalDegrees[project] === 0)
queue.push(project)

//perform topological sorting
while(queue.length> 0){
let current = queue.shift();
order.push(current);
for(let neighbor of graph[current]){
totalDegrees[neighbor]--;
if(totalDegrees[neighbor]===0)
queue.push(neighbor)
}
}
if(order.length!== projects.length) throw Error

return order;
}
36 changes: 36 additions & 0 deletions chapter04/4.08 - First Common Ancestor/firstCommonAncestor2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class Node {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}

function findCommonAncestor(root, node1, node2) {
if (!root) return null;
if (root === node1 || root === node2) return root;

let left = findCommonAncestor(root.left, node1, node2);
let right = findCommonAncestor(root.right, node1, node2);

if (left && right) return root;

return left ? left : right;
}

const root = new Node(1);
root.left = new Node(2);
root.right = new Node(3);
root.left.left = new Node(4);
root.left.right = new Node(5);
root.right.left = new Node(6);
root.right.right = new Node(7);

let node1 = root.right.left; //6
let node2 = root.right.right; //7
console.log(findCommonAncestor(root, node1, node2).value); // Outputs: 3

node1 = root.left.left; // 4
node2 = root.left.right; // 5

console.log(findCommonAncestor(root, node1, node2).value); // Outputs: 2
9 changes: 9 additions & 0 deletions chapter04/4.13 - Real World Examples/about.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
In-order, pre-order, and post-order traversal are all methods of traversing a binary tree, but they differ in the order in which they visit the nodes.

In-order traversal visits the left subtree first, then the current node, and finally the right subtree. This order is useful for sorting elements in a binary search tree, as it visits the nodes in a sorted order.

Pre-order traversal visits the current node first, then the left subtree, and finally the right subtree. This order is useful for representing a file system hierarchy, where the parent node is visited before its children.

Post-order traversal visits the left subtree first, then the right subtree, and finally the current node. This order is useful for deleting a tree or a subtree efficiently.

In summary, In-order traversal visits left subtree first, then root and then right subtree. Pre-order traversal visits root first, then left subtree and then right subtree. Post-order traversal visits left subtree first, then right subtree and then root.
33 changes: 33 additions & 0 deletions chapter04/4.13 - Real World Examples/realworld.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Binary Tree In-order traversal is a way to visit all the nodes of a binary tree in a specific order. Some real-world examples where this traversal method could be used include:

Sorting algorithms: In-order traversal can be used to sort elements in a binary search tree, since it visits the nodes in a sorted order.

Database indexing: In-order traversal can be used to traverse a B-Tree, which is a type of self-balancing search tree used in databases to optimize search performance.

Compiler Design: In-order traversal can be used to generate the three address code in the intermediate code generation phase of a compiler.

Expression Trees: In-order traversal can be used to evaluate mathematical expressions represented as expression trees. It will give the infix notation of the expression.

Other Applications: In-order traversal is also used in a few other applications like creating a thread-safe iterator, serializing a tree into a list, etc.

It is important to note that there are other traversal methods such as pre-order, post-order and level-order traversal that can also be used for various use cases depending on the requirements and the tree structure.

File System: Pre-order traversal can be used to represent a file system hierarchy, where the parent node is visited before its children. This corresponds to the way file systems are typically represented in a file explorer, where the directory is listed before its contents.



A min-heap is a specific type of binary heap data structure where the root node is the node with the smallest value. Here are a few examples of real-life scenarios where a min-heap could be used:

Priority Queue: A min-heap can be used to implement a priority queue, where each element has a priority associated with it. The element with the smallest priority is always on top of the heap.

Dijkstra's algorithm: Dijkstra's algorithm is a shortest-path algorithm that uses a min-heap to efficiently find the next closest vertex to visit.

Huffman coding: Huffman coding is an algorithm used for lossless data compression that uses a min-heap to efficiently construct a Huffman tree.

Median maintenance: A min-heap can be used to efficiently maintain the median of a stream of numbers.

Event-driven simulation: A min-heap can be used to efficiently schedule the next event in a simulation by ordering events by their timestamp

Resource allocation: A min-heap can be used to allocate resources efficiently, where resources with the least usage are allocated first.


Loading