diff --git a/longest-substring-without-repeating-characters/Jeehay28.js b/longest-substring-without-repeating-characters/Jeehay28.js new file mode 100644 index 000000000..60536cf6a --- /dev/null +++ b/longest-substring-without-repeating-characters/Jeehay28.js @@ -0,0 +1,64 @@ +/** + * @param {string} s + * @return {number} + */ + +// 🌟 sliding window technique + +// Time Complexity: O(n) +// Why it's O(n) +// - The end pointer iterates over the string exactly once (O(n)). +// - The start pointer also only moves forward without re-processing elements (O(n)). +// - Therefore, the total amount of work done is proportional to the length of the string (n). +// - So, even though there is a while loop inside the for loop, the total work done (number of operations) is still linear, O(n), because the start and end pointers together move across the string just once. +// - This is the key reason why the time complexity is O(n), even with nested loops. + +// Space Complexity: O(k), where k k is the length of the longest substring without repeating characters. +// In the worst case, k = n, so O(n) + +var lengthOfLongestSubstring = function (s) { + let start = 0; + let longest = 0; + let subString = new Set(); + + for (let end = 0; end < s.length; end++) { + while (subString.has(s[end])) { + // Shrink the window by moving start + subString.delete(s[start]); + start += 1; + } + + subString.add(s[end]); + longest = Math.max(longest, end - start + 1); + } + + return longest; +}; + +// 🛠️ Solution 1 +// Time Complexity: O(n^2), where n is the length of the string s +// Space Complexity: O(k), where k is the length of the longest substring without repeating characters (k ≤ n) + +// why the space complexity is not just O(n): +// - Saying O(n) is technically correct in the worst case, +// - but it hides the fact that the actual space usage is proportional to the length of the longest substring without repeats, +// - which could be much smaller than n in many practical cases (e.g., for a string like "aaabbbccc"). + +// var lengthOfLongestSubstring = function (s) { +// let longest = 0; + +// for (let i = 0; i < s.length; i++) { +// let subString = new Set(); + +// for (let j = i; j < s.length; j++) { +// if (subString.has(s[j])) { +// break; +// } else { +// subString.add(s[j]); +// longest = Math.max(longest, j - i + 1); +// } +// } +// } +// return longest; +// }; + diff --git a/number-of-islands/Jeehay28.js b/number-of-islands/Jeehay28.js new file mode 100644 index 000000000..b7d07b926 --- /dev/null +++ b/number-of-islands/Jeehay28.js @@ -0,0 +1,50 @@ +/** + * @param {character[][]} grid + * @return {number} + */ + +// 🌻 +// Time Complexity: O(m * n), where M is the number of rows and N is the number of columns in the grid. +// Space Complexity: O(k), where k is the size of the largest island (k <= m * n) + +// The space complexity is determined by the depth of the recursive stack used by the sink function. +// In the worst-case scenario, where the entire grid is filled with "1"s (a single large island), +// the depth of recursion could go up to O(m * n). + +var numIslands = function (grid) { + // depth-first search (DFS) that potentially visits every connected cell in the current island + const sink = (row, col) => { + grid[row][col] = "0"; + + const adjacent = [ + [row - 1, col], // up + [row + 1, col], // down + [row, col - 1], // left + [row, col + 1], // right + ]; + + for ([r, c] of adjacent) { + if (r >= 0 && r < grid.length && c >= 0 && c < grid[r].length) { + if (grid[r][c] === "1") { + sink(r, c); + } + } + } + }; + + let cnt = 0; + + for (let i = 0; i < grid.length; i++) { + for (let j = 0; j < grid[0].length; j++) { + if (grid[i][j] === "1") { + cnt += 1; + sink(i, j); + } + } + } + + return cnt; +}; + + + diff --git a/reverse-linked-list/Jeehay28.js b/reverse-linked-list/Jeehay28.js new file mode 100644 index 000000000..5d4c43cef --- /dev/null +++ b/reverse-linked-list/Jeehay28.js @@ -0,0 +1,82 @@ +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode} head + * @return {ListNode} + */ + +// Time Complexity: O(n) +// Space Complexity: O(1) + +// The algorithm uses a constant amount of extra space: prev, current, and nextTemp. +// No additional data structures (like arrays or new linked lists) are created. +// Hence, the space complexity is O(1). + +var reverseList = function (head) { + let prev = null; + let current = head; + + while (current) { + let nextTemp = current.next; + + current.next = prev; + console.log(current, prev); + + prev = current; + console.log(current, prev); + current = nextTemp; + } + + return prev; // New head of the reversed list +}; + +// head = [1,2,3,4,5] +// [1] null +// [1] [1] +// [2,1] [1] +// [2,1] [2,1] +// [3,2,1] [2,1] +// [3,2,1] [3,2,1] +// [4,3,2,1] [3,2,1] +// [4,3,2,1] [4,3,2,1] +// [5,4,3,2,1] [5,4,3,2,1] + +// my own approach +// Time Complexity: O(n) +// Space Complexity: O(n) +var reverseList = function (head) { + if (head === null) { + return null; + } + + let current = head; + let arr = []; + // console.log(head.val) + + // traverse the linked List - TC: O(n) + while (current) { + arr.push(current.val); + current = current.next; + } + + // reverse the array - TC: O(n) + arr = arr.reverse(); + + let head1 = new ListNode(arr[0]); + let current1 = head1; + + // rebuild the linked list - TC: O(n) + for (let i = 1; i < arr.length; i++) { + current1.next = new ListNode(arr[i]); + current1 = current1.next; + } + + return head1; +}; + + diff --git a/set-matrix-zeroes/Jeehay28.js b/set-matrix-zeroes/Jeehay28.js new file mode 100644 index 000000000..367edeba2 --- /dev/null +++ b/set-matrix-zeroes/Jeehay28.js @@ -0,0 +1,106 @@ +// 🌈 In-Place Solution (Without extra space) +// The term "in-place" refers to algorithms or solutions that modify the input data directly, +// without needing extra space for a separate copy of the data. In an in-place solution, +// the operations are performed using a fixed amount of extra memory, typically O(1) space, beyond the input data itself. + +/** + * @param {number[][]} matrix + * @return {void} Do not return anything, modify matrix in-place instead. + */ + +// Time Complexity: O(m * n) +// Space Complexity: O(1) +var setZeroes = function (matrix) { + const rows = matrix.length; + const cols = matrix[0].length; + + let firstRowHasZero = false; + let firstColHasZero = false; + + // Check if the first row has any zero + for (let c = 0; c < cols; c++) { + if (matrix[0][c] === 0) { + firstRowHasZero = true; + break; + } + } + + // Check if the first column has any zero + for (let r = 0; r < rows; r++) { + if (matrix[r][0] === 0) { + firstColHasZero = true; + break; + } + } + + // Use first row and column to mark zeros + for (let i = 1; i < rows; i++) { + for (let j = 1; j < cols; j++) { + if (matrix[i][j] === 0) { + matrix[0][j] = 0; + matrix[i][0] = 0; + } + } + } + + // Set zeros based on marks in the first row and column + for (let i = 1; i < rows; i++) { + for (let j = 1; j < cols; j++) { + if (matrix[0][j] === 0 || matrix[i][0] === 0) { + matrix[i][j] = 0; + } + } + } + + // Handle first row + if (firstRowHasZero) { + for (let c = 0; c < cols; c++) { + matrix[0][c] = 0; + } + } + + // Handle first column + if (firstColHasZero) { + for (let r = 0; r < rows; r++) { + matrix[r][0] = 0; + } + } + + return matrix; +}; + +/** + * @param {number[][]} matrix + * @return {void} Do not return anything, modify matrix in-place instead. + */ + +// 💪 My initial approach with Set... +// Time Complexity: O(m * n) +// Space Complexity: O(m + n) +// var setZeroes = function (matrix) { +// let rows = new Set(); +// let cols = new Set(); + +// for (let i = 0; i < matrix.length; i++) { +// for (let j = 0; j < matrix[0].length; j++) { +// if (matrix[i][j] === 0) { +// rows.add(i); +// cols.add(j); +// } +// } +// } + +// for (row of rows) { +// matrix[row] = new Array(matrix[0].length).fill(0); +// } + +// for (col of cols) { +// for (let row = 0; row < matrix.length; row++) { +// matrix[row][col] = 0; +// } +// } + +// return matrix; +// }; + + diff --git a/unique-paths/Jeehay28.js b/unique-paths/Jeehay28.js new file mode 100644 index 000000000..069eb6716 --- /dev/null +++ b/unique-paths/Jeehay28.js @@ -0,0 +1,86 @@ +/** + * @param {number} m + * @param {number} n + * @return {number} + */ + +// 🍎 DP +// Time Complexity: O(m*n) +// Space Complexity: O(n) + +// Row 0: [1, 1, 1, 1] +// Row 1: [1, 2, 3, 4] +// Row 2: [1, 3, 6, 10] + +// Initial dp: [1, 1, 1, 1] +// left = 1 (always starts with 1 for the first column) + +// col = 1: left += dp[1] → left = 1 + 1 = 2 → dp[1] = left +// col = 2: left += dp[2] → left = 2 + 1 = 3 → dp[2] = left +// col = 3: left += dp[3] → left = 3 + 1 = 4 → dp[3] = left + +// dp after row 1: [1, 2, 3, 4] + +// Initial dp: [1, 2, 3, 4] +// left = 1 + +// col = 1: left += dp[1] → left = 1 + 2 = 3 → dp[1] = left +// col = 2: left += dp[2] → left = 3 + 3 = 6 → dp[2] = left +// col = 3: left += dp[3] → left = 6 + 4 = 10 → dp[3] = left + +// dp after row 2: [1, 3, 6, 10] + +var uniquePaths = function (m, n) { + let dp = new Array(n).fill(1); + + for (let row = 1; row < m; row++) { + let left = 1; + for (let col = 1; col < n; col++) { + left += dp[col]; + dp[col] = left; + } + } + + return dp[n - 1]; +}; + +/** + * @param {number} m + * @param {number} n + * @return {number} + */ + +// 🍎 DFS +// Time Complexity: O(m*n) +// Space Complexity: O(m*n) + +// var uniquePaths = function (m, n) { +// const memo = {}; + +// const dfs = (row, col) => { +// if (row === m - 1 && col === n - 1) { +// return 1; +// } + +// let cnt = 0; + +// const key = `${row}, ${col}`; +// if (key in memo) { +// return memo[key]; +// } + +// if (row + 1 < m) { +// cnt += dfs(row + 1, col); +// } + +// if (col + 1 < n) { +// cnt += dfs(row, col + 1); +// } + +// memo[key] = cnt; + +// return cnt; +// }; + +// return dfs(0, 0); +// };