Skip to content

[Jeehay28] WEEK 08 #960

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

Merged
merged 5 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions clone-graph/Jeehay28.js
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DFS 로 접근만 생각했었는데, BFS 접근도 신선하네요!

// 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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JS 에서 shift 연산의 시간복잡도는 O(n) 입니다. 따라서 현재 BFS 로 풀었을 때 시간 복잡도는 O(N^2 + E) 가 될 것 같은데요, shift 연산을 최적화하기 위해 Queue 를 직접 구현해보시는 것을 어떠실까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아, 감사합니다. 시도해보겠습니다.

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);
};

95 changes: 95 additions & 0 deletions longest-common-subsequence/Jeehay28.js
Original file line number Diff line number Diff line change
@@ -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);
// };


32 changes: 32 additions & 0 deletions longest-repeating-character-replacement/Jeehay28.js
Original file line number Diff line number Diff line change
@@ -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;
};

68 changes: 68 additions & 0 deletions number-of-1-bits/Jeehay28.js
Original file line number Diff line number Diff line change
@@ -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;
// };


23 changes: 23 additions & 0 deletions sum-of-two-integers/Jeehay28.js
Original file line number Diff line number Diff line change
@@ -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;
};