diff --git a/course-schedule/Jeehay28.js b/course-schedule/Jeehay28.js new file mode 100644 index 000000000..98523d297 --- /dev/null +++ b/course-schedule/Jeehay28.js @@ -0,0 +1,69 @@ +/** + * @param {number} numCourses + * @param {number[][]} prerequisites + * @return {boolean} + */ + +// ✅ Graph DFS (Depth-First Search) approach +// Time Complexity: O(V + E), where V is the number of courses (numCourses) and E is the number of prerequisites (edges). +// Space Complexity: O(V + E), where V is the number of courses and E is the number of prerequisites. + +var canFinish = function (numCourses, prerequisites) { + // prerequisites = [ + // [1, 0], // Course 1 depends on Course 0 + // [2, 1], // Course 2 depends on Course 1 + // [3, 1], // Course 3 depends on Course 1 + // [3, 2] // Course 3 depends on Course 2 + // ]; + + // graph = { + // 0: [1], // Course 0 is a prerequisite for Course 1 + // 1: [2, 3], // Course 1 is a prerequisite for Courses 2 and 3 + // 2: [3], // Course 2 is a prerequisite for Course 3 + // 3: [] // Course 3 has no prerequisites + // }; + + // Build the graph + const graph = {}; + + // for(let i=0; i { + if (visited[course] === 1) return false; // cycle detected! + if (visited[course] === 2) return true; // already visited + + visited[course] = 1; + + // // Visit all the courses that depend on the current course + for (const nextCourse of graph[course] || []) { + if (!dfs(nextCourse)) { + return false; // cycle detected! + } + } + + visited[course] = 2; // fully visited and return true + return true; + }; + + // Check for each course whether it's possible to finish it + for (let i = 0; i < numCourses; i++) { + if (!dfs(i)) { + return false; // cycle detected! + } + } + + return true; // no cycles +}; + diff --git a/invert-binary-tree/Jeehay28.js b/invert-binary-tree/Jeehay28.js new file mode 100644 index 000000000..ab798240e --- /dev/null +++ b/invert-binary-tree/Jeehay28.js @@ -0,0 +1,91 @@ +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {TreeNode} + */ + +// ✔️ Recursive Approach +// Time Complexity: O(N), N = Total number of nodes (each node is processed once) +// Space Complexity: O(H), H = Height of the tree (due to recursion stack depth) + +var invertTree = function (root) { + if (!root) return null; + + [root.left, root.right] = [root.right, root.left]; + + invertTree(root.left); + invertTree(root.right); + + return root; +}; + +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {TreeNode} + */ + +// ✔️ Stack → DFS approach +// Time Complexity: O(N), N = Total number of nodes (each node is processed once) +// Space Complexity: O(H), H = Height of the tree (due to recursion stack depth) + +// var invertTree = function (root) { +// let stack = [root]; + +// while (stack.length > 0) { +// const node = stack.pop(); +// if (!node) continue; + +// [node.left, node.right] = [node.right, node.left]; +// stack.push(node.left); +// stack.push(node.right); +// } +// return root; +// }; + + +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {TreeNode} + */ + +// ✔️ Queue → BFS +// Time Complexity: O(N), N = Total number of nodes (each node is processed once) +// Space Complexity: O(W), W = Maximum width of the tree +// var invertTree = function (root) { +// let queue = [root]; + +// while (queue.length > 0) { +// const node = queue.shift(); +// if (!node) continue; + +// [node.left, node.right] = [node.right, node.left]; +// queue.push(node.left); +// queue.push(node.right); +// } +// return root; +// }; + + diff --git a/jump-game/Jeehay28.js b/jump-game/Jeehay28.js new file mode 100644 index 000000000..5f30b32b3 --- /dev/null +++ b/jump-game/Jeehay28.js @@ -0,0 +1,101 @@ +// 🚀 Greedy approach: much more efficient than the recursive approach +// Time Complexity: O(n) +// Space Complexity: O(1), No extra memory used + +/** + * @param {number[]} nums + * @return {boolean} + */ +var canJump = function (nums) { + // ➡️ The farthest position you can reach from any of the previous indices you have already visited. + let farthest = 0; + + for (let i = 0; i < nums.length; i++) { + // You cannot reach this position even with your farthest jump value. + if (i > farthest) return false; + + // Compare current maximum jump with the previous maximum. + farthest = Math.max(farthest, nums[i] + i); + + // Check if you can reach the last index with the current farthest jump. + if (farthest >= nums.length - 1) return true; + } + return false; +}; + + +/** + * @param {number[]} nums + * @return {boolean} + */ + +// Time Complexity: O(n) +// Space Complexity: O(n) +var canJump = function (nums) { + let lastIndex = nums.length - 1; + + // Initialize memoization array to track visited indices + let memo = new Array(nums.length).fill(undefined); + + const dfs = (i) => { + // Base case: if we've reached or surpassed the last index, return true + if (i >= lastIndex) return true; + + // If the current index has already been visited, return the stored result + if (memo[i] !== undefined) return memo[i]; + + // Calculate the farthest position that can be reached from the current index + let maxJump = Math.min(nums[i] + i, lastIndex); + + for (let j = i + 1; j <= maxJump; j++) { + if (dfs(j)) { + memo[i] = true; + return true; + } + } + + memo[i] = false; + return false; + }; + + return dfs(0); +}; + + +// 🌀 recursive approach +// ⚠️ Time Complexity: O(2^n) - Exponential due to recursive branching without memoization +// 🔵 Space Complexity: O(n) - Recursive call stack depth + +/** + * Check if you can jump to the last index from the first index. + * @param {number[]} nums - Array where nums[i] is the max jump length from position i. + * @return {boolean} True if you can reach the last index, otherwise false. + */ + +// var canJump = function (nums) { +// const dfs = (start) => { +// // Base Case: Reached the last index +// if (start === nums.length - 1) { +// return true; +// } + +// // Recursive Case: Try all possible jumps +// for (let i = 1; i <= nums[start]; i++) { +// // Example with nums = [2, 3, 1, 1, 4]: +// // start = 1, nums[1] = 3 (can jump 1, 2, or 3 steps) +// // Possible calls: +// // dfs(1 + 1) -> check from index 2 +// // dfs(1 + 2) -> check from index 3 +// // dfs(1 + 3) -> reached the last index (success) + +// if (dfs(start + i)) { +// return true; +// } +// } + +// return false; // cannot reach the last index +// }; + +// return dfs(0); +// }; + diff --git a/merge-k-sorted-lists/Jeehay28.js b/merge-k-sorted-lists/Jeehay28.js new file mode 100644 index 000000000..4d45f3277 --- /dev/null +++ b/merge-k-sorted-lists/Jeehay28.js @@ -0,0 +1,72 @@ +// Brute Force (Array Sorting) - Good for smaller cases +// 🕒 Time Complexity: O(N log N), where N is the total number of nodes +// 🗂️ Space Complexity: O(N) + +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode[]} lists + * @return {ListNode} + */ +var mergeKLists = function (lists) { + const nums = []; + + // ┌──────────────────────────────────────────────────────────────────┐ + // │ Step 1: Extract values from all linked lists │ + // │ We traverse each linked list and push node values into 'nums'. │ + // │ This flattens the K lists into a single array. │ + // └──────────────────────────────────────────────────────────────────┘ + for (let list of lists) { + while (list) { + nums.push(list.val); + list = list.next; + } + } + + // ┌──────────────────────────────────────────────────────────────────┐ + // │ Step 2: Sort the values │ + // │ JavaScript's default sort is lexicographical, so we use a custom │ + // │ comparator to sort numbers correctly in ascending order. │ + // └──────────────────────────────────────────────────────────────────┘ + nums.sort((a, b) => a - b); + + // ┌──────────────────────────────────────────────────────────────────┐ + // │ Step 3: Create a new sorted linked list │ + // │ Initialize a dummy node, then iterate through the sorted array. │ + // │ For each value, create a new ListNode and append it to the list. │ + // └──────────────────────────────────────────────────────────────────┘ + let dummy = new ListNode(-1); + let current = dummy; + + for (num of nums) { + current.next = new ListNode(num); + current = current.next; + } + + // ┌──────────────────────────────────────────────────────────────────┐ + // │ Step 4: Return the merged list │ + // │ We return dummy.next since dummy is a placeholder. │ + // └──────────────────────────────────────────────────────────────────┘ + return dummy.next; +}; + +/** + * ┌──────────────────────────────────────────────────────────────────┐ + * │ Time & Space Complexity │ + * ├──────────────────────────────────────────────────────────────────┤ + * │ Operation │ Complexity │ + * ├──────────────────────────────────────────────────────────────────┤ + * │ Extracting values │ O(N) - N is the total number of nodes │ + * │ Sorting values │ O(N log N) - JavaScript's sort uses Timsort │ + * │ Building linked list │ O(N) - Iterates through sorted array │ + * ├──────────────────────────────────────────────────────────────────┤ + * │ Overall Time Complexity │ O(N log N) │ + * ├──────────────────────────────────────────────────────────────────┤ + * │ Space Complexity │ O(N) - Storing all node values in array │ + * └──────────────────────────────────────────────────────────────────┘ + */ diff --git a/search-in-rotated-sorted-array/Jeehay28.js b/search-in-rotated-sorted-array/Jeehay28.js new file mode 100644 index 000000000..376586a77 --- /dev/null +++ b/search-in-rotated-sorted-array/Jeehay28.js @@ -0,0 +1,45 @@ +/** + * @param {number[]} nums + * @param {number} target + * @return {number} + */ + +// ✅ Iterative Binary Search for Rotated Sorted Array +// Time Complexity: O(log N) +// Space Complexity: O(1) +var search = function (nums, target) { + let left = 0; + let right = nums.length - 1; + + while (left <= right) { + let pointer = Math.floor((left + right) / 2); + + if (nums[pointer] === target) { + return pointer; + } + + // Check if the left half is sorted + if (nums[left] <= nums[pointer]) { + // Target is in the sorted left half + if (nums[left] <= target && target < nums[pointer]) { + right = pointer - 1; + } else { + // Target is not in the left half, so search in the right half + left = pointer + 1; + } + } else { + // The right half must be sorted + + // Target is in the sorted right half + if (nums[pointer] < target && target <= nums[right]) { + left = pointer + 1; + } else { + // Target is not in the right half, so search in the left half + right = pointer - 1; + } + } + } + + return -1; +}; +