diff --git a/clone-graph/Jeehay28.js b/clone-graph/Jeehay28.js new file mode 100644 index 000000000..4b08eab35 --- /dev/null +++ b/clone-graph/Jeehay28.js @@ -0,0 +1,68 @@ +/** + * // Definition for a _Node. + * function _Node(val, neighbors) { + * this.val = val === undefined ? 0 : val; + * this.neighbors = neighbors === undefined ? [] : neighbors; + * }; + */ + +/** + * @param {_Node} node + * @return {_Node} + */ + +// BFS approach +// Time Complexity: O(N + E), where N is the number of nodes and E is the number of edges. +// Space Complexity: O(N), due to the clones map and additional storage (queue for BFS, recursion stack for DFS). + +var cloneGraph = function (node) { + if (!node) { + return null; + } + let clone = new Node(node.val); + let clones = new Map(); + clones.set(node, clone); + let queue = [node]; + while (queue.length > 0) { + node = queue.shift(); + for (const neighbor of node.neighbors) { + if (!clones.get(neighbor)) { + const temp = new Node(neighbor.val); + clones.set(neighbor, temp); + queue.push(neighbor); + } + clones.get(node).neighbors.push(clones.get(neighbor)); + } + } + + return clone; +}; + +// DFS approach +// Time Complexity: O(N + E), where N is the number of nodes and E is the number of edges. +// Space Complexity: O(N), due to the clones map and the recursion stack. + +var cloneGraph = function (node) { + if (!node) { + return null; + } + + let clones = new Map(); + + const dfs = (node) => { + if (clones.has(node)) { + return clones.get(node); + } + let clone = new Node(node.val); + clones.set(node, clone); + + for (neighbor of node.neighbors) { + clone.neighbors.push(dfs(neighbor)); + } + + return clone; + }; + + return dfs(node); +}; + diff --git a/longest-common-subsequence/Jeehay28.js b/longest-common-subsequence/Jeehay28.js new file mode 100644 index 000000000..3710cea3f --- /dev/null +++ b/longest-common-subsequence/Jeehay28.js @@ -0,0 +1,95 @@ +/** + * @param {string} text1 + * @param {string} text2 + * @return {number} + */ + +// 🤔 +// Both memoization (top-down) and dynamic programming (bottom-up) have the same time and space complexity of O(m * n). +// The difference lies in their implementation: +// - Memoization uses recursion with a cache to avoid redundant calculations but may incur overhead from recursive calls and stack space. +// - Dynamic Programming iteratively builds the solution, avoiding recursion overhead and sometimes offering better performance. +// DP is often preferred when recursion depth or function call overhead is a concern, while memoization can be more intuitive for certain problems. + +// 😊 memoization approach +// Time Complexity: O(m * n), where m is the length of text1, and n is the length of text2 +// Space Complexity: O(m * n) +// Top-down approach with recursion. +// Use a cache (or memoization) to store intermediate results. + +var longestCommonSubsequence = function (text1, text2) { + const memo = new Map(); + + const dfs = (i, j) => { + const key = `${i},${j}`; // Convert (i, j) into a unique string key + if (memo.has(key)) { + return memo.get(key); + } + + if (i === text1.length || j === text2.length) { + memo.set(key, 0); + } else if (text1[i] === text2[j]) { + memo.set(key, 1 + dfs(i + 1, j + 1)); + } else { + memo.set(key, Math.max(dfs(i + 1, j), dfs(i, j + 1))); + } + + return memo.get(key); + }; + return dfs(0, 0); +}; + +// 😊 bottom-up dynamic programming approach +// Time Complexity: O(m * n), where m is the length of text1, and n is the length of text2 +// Space Complexity: O(m * n) + +// text1 = "abcde" +// text2 = "ace" + +// "" a c e +// "" 0 0 0 0 +// a 0 1 1 1 +// b 0 1 1 1 +// c 0 1 2 2 +// d 0 1 2 2 +// e 0 1 2 3 + + +// var longestCommonSubsequence = function (text1, text2) { +// const dp = new Array(text1.length + 1) +// .fill(0) +// .map(() => new Array(text2.length + 1).fill(0)); + +// for (let i = 1; i <= text1.length; i++) { +// for (let j = 1; j <= text2.length; j++) { +// if (text1[i - 1] === text2[j - 1]) { +// dp[i][j] = dp[i - 1][j - 1] + 1; +// } else { +// dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); +// } +// } +// } + +// return dp[text1.length][text2.length]; +// }; + + +// 😱 Time Limit Exceeded! +// Brute-force Recursion +// Time Complexity: O(2^(m+n)) (Exponential) +// Space Complexity: O(m + n) (Recursive Stack) + +// var longestCommonSubsequence = function (text1, text2) { +// const dfs = (i, j) => { +// if (i === text1.length || j === text2.length) { +// return 0; +// } +// if (text1[i] === text2[j]) { +// return 1 + dfs(i + 1, j + 1); +// } +// return Math.max(dfs(i + 1, j), dfs(i, j + 1)); +// }; +// return dfs(0, 0); +// }; + + diff --git a/longest-repeating-character-replacement/Jeehay28.js b/longest-repeating-character-replacement/Jeehay28.js new file mode 100644 index 000000000..94e68fd1a --- /dev/null +++ b/longest-repeating-character-replacement/Jeehay28.js @@ -0,0 +1,32 @@ +/** + * @param {string} s + * @param {number} k + * @return {number} + */ + +// sliding window technique +// Time complexity: O(n), where n is the length of the string. Both the start and end pointers traverse the string at most once. +// Space Complexity: O(1), as we only need a fixed amount of extra space for the character frequency map and some variables. +var characterReplacement = function (s, k) { + let longest = 0; + let maxCount = 0; + const charCount = {}; + let start = 0; + + for (let end = 0; end < s.length; end++) { + const char = s[end]; + charCount[char] = (charCount[char] || 0) + 1; + maxCount = Math.max(charCount[char], maxCount); + + while (end - start + 1 - maxCount > k) { + const temp = s[start]; + charCount[temp] -= 1; + start += 1; + } + + longest = Math.max(longest, end - start + 1); + } + + return longest; +}; + diff --git a/number-of-1-bits/Jeehay28.js b/number-of-1-bits/Jeehay28.js new file mode 100644 index 000000000..d9c08bc57 --- /dev/null +++ b/number-of-1-bits/Jeehay28.js @@ -0,0 +1,68 @@ +// 💪 Optimized Approach: Brian Kernighan's Algorithm +// Brian Kernighan's algorithm is a very efficient way to count the number of set bits in a number. +// It works by repeatedly turning off the rightmost set bit. + +// Time Complexity: O(k), where k is the number of set bits (1s) in the binary representation of n. +// - Binary representation of 5 : 101 + +// Space Complexity: O(1) + +/** + * + * @param {number} n - The number whose set bits need to be counted. + * @return {number} - The number of set bits (1s) in the binary representation of n. + */ + +var hammingWeight = function (n) { + let cnt = 0; + + while (n > 0) { + cnt += 1; + n = n & (n - 1); // removes the rightmost set bit (1) in the binary representation of n, and the other bits remain unchanged + } + return cnt; +}; + +// 💪 Improved versiion +// TC: O(log n) +// SC: O(1) +// var hammingWeight = function(n) { + +// let cnt = 0; + +// while(n > 0) { +// cnt += n % 2; +// n = Math.floor(n / 2) +// } +// return cnt; + +// }; + +// 💪 My own approach +// Time Complexity: O(log n) +// Space Complexity: O(log n) + +/** + * Time Complexity: O(log n) + * - The operation `n.toString(2)` converts the number into its binary representation, which takes O(log n) time, where 'n' is the input number. + * - The `replaceAll("0", "")` operation goes through the binary string to remove all '0' characters, which is O(log n) as the binary string has a length of log(n). + * - The `.length` operation to count the '1' characters is O(log n) as well. + * - Overall, the time complexity is O(log n) since we are iterating over the binary string twice (once to convert and once to remove zeros). + + * Space Complexity: O(log n) + * - The binary representation of the number is stored as a string, which takes O(log n) space, where log(n) is the length of the binary string. + * - Therefore, the space complexity is O(log n) because of the space used to store the binary string during the conversion process. + */ + +/** + * @param {number} n + * @return {number} + */ + +// var hammingWeight = function (n) { +// let binary = n.toString(2).replaceAll("0", "").length; + +// return binary; +// }; + + diff --git a/sum-of-two-integers/Jeehay28.js b/sum-of-two-integers/Jeehay28.js new file mode 100644 index 000000000..bc1b91c18 --- /dev/null +++ b/sum-of-two-integers/Jeehay28.js @@ -0,0 +1,23 @@ +/** + * @param {number} a + * @param {number} b + * @return {number} + */ + +// Time Complexity: O(log(max(a, b))) +// Space Complexity: O(1) + +var getSum = function (a, b) { + // XOR (^): outputs true (or 1) if the inputs are different, and false (or 0) if the inputs are the same. + // And (&): compares each bit of two numbers and returns 1 only if both bits are 1; otherwise, it returns 0. + // left shitf (<<): moves the bits one position to the left, which is the same as multiplying by 2. + + while (b !== 0) { + let carry = (a & b) << 1; + a = a ^ b; + b = carry; + } + + return a; +}; +