diff --git a/clone-graph/clara-shin.js b/clone-graph/clara-shin.js new file mode 100644 index 000000000..23ddfe320 --- /dev/null +++ b/clone-graph/clara-shin.js @@ -0,0 +1,37 @@ +/** + * BFS를 사용한 그래프 복제 + * @param {_Node} node + * @return {_Node} + */ +var cloneGraph = function (node) { + if (!node) return null; + + // 원본 노드와 복제된 노드를 매핑하는 해시맵 + const cloned = new Map(); + + // BFS를 위한 큐 + const queue = [node]; + + // 시작 노드 복제 + cloned.set(node, new _Node(node.val)); + + while (queue.length > 0) { + const currentNode = queue.shift(); + + // 현재 노드의 모든 이웃들을 처리 + for (let neighbor of currentNode.neighbors) { + // 이웃이 아직 복제되지 않았다면 + if (!cloned.has(neighbor)) { + // 새로운 노드 생성하고 맵에 저장 + cloned.set(neighbor, new _Node(neighbor.val)); + // 큐에 추가하여 나중에 처리 + queue.push(neighbor); + } + + // 복제된 현재 노드의 이웃 리스트에 복제된 이웃 추가 + cloned.get(currentNode).neighbors.push(cloned.get(neighbor)); + } + } + + return cloned.get(node); +}; diff --git a/longest-common-subsequence/clara-shin.js b/longest-common-subsequence/clara-shin.js new file mode 100644 index 000000000..347391856 --- /dev/null +++ b/longest-common-subsequence/clara-shin.js @@ -0,0 +1,40 @@ +/** + * 두 문자열에서 공통으로 나타나는 부분 수열 중 가장 긴 것의 길이를 찾는 문제 + * + * 다이나믹 프로그래밍(DP): 작은 문제의 결과를 저장해서 큰 문제를 해결하는 방법 + * 시간 복잡도: O(m × n) + * 공간복잡도: O(min(m, n)) + */ +/** + * @param {string} text1 + * @param {string} text2 + * @return {number} + */ +var longestCommonSubsequence = function (text1, text2) { + // 더 짧은 문자열을 text2로 만들어 공간 절약 + if (text1.length < text2.length) { + [text1, text2] = [text2, text1]; + } + + const m = text1.length; + const n = text2.length; + + // 1차원 배열 2개만 사용 (이전 행, 현재 행) + let prev = Array(n + 1).fill(0); + let curr = Array(n + 1).fill(0); + + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + if (text1[i - 1] === text2[j - 1]) { + curr[j] = prev[j - 1] + 1; + } else { + curr[j] = Math.max(prev[j], curr[j - 1]); + } + } + // 다음 반복을 위해 배열 교체 + [prev, curr] = [curr, prev]; + curr.fill(0); + } + + return prev[n]; +}; diff --git a/longest-repeating-character-replacement/clara-shin.js b/longest-repeating-character-replacement/clara-shin.js new file mode 100644 index 000000000..d113f331f --- /dev/null +++ b/longest-repeating-character-replacement/clara-shin.js @@ -0,0 +1,51 @@ +/** + * 최대 k번 문자 변경 후, 가장 긴 동일문자 부분문자열 찾기 + * 문자열 s와 정수 k, 최대 k번까지 문자를 다른 영문대문자로 바꿀 수 있음 + * 동일한 문자로 구성된 가장 긴 부분문자열 길이를 리턴해라 + * + * 슬라이딩 윈도우 사용(문자열을 한번만 순회) + * 윈도우 내에서, 가장 많이 등장하는 문자를 제외한 나머지 문자들이, k개 이하일 때만, 유효한 부분 문자열이다 + * 조건을 만족하지 않으면 윈도우의 왼쪽 포인터를 오른쪽으로 이동하여 윈도우 크기를 줄인다 + * + * 시간복잡도 O(n) + * 공간복잡도 O(1) - 문자 빈도수 저장배열 크기는 항상 26 고정 + */ + +/** + * @param {string} s + * @param {number} k + * @return {number} + */ +var characterReplacement = function (s, k) { + // 문자 빈도수를 저장할 배열 (A-Z: 65-90) 0으로 초기화 + const charCount = new Array(26).fill(0); + + let left = 0; // 윈도우 왼쪽 끝 + let maxCount = 0; // 현재 윈도우에서 가장 많이 등장하는 문자의 빈도수 + let maxLength = 0; // 결과: 가장 긴 부분 문자열 길이 + + // 윈도우의 오른쪽 끝 이동 + for (let right = 0; right < s.length; right++) { + // 현재 문자의 빈도수 증가 (문자 코드를 인덱스로 변환) + const charIndex = s.charCodeAt(right) - 65; + charCount[charIndex]++; + + // 현재 윈도우에서 가장 많이 등장하는 문자의 빈도수 업데이트 + maxCount = Math.max(maxCount, charCount[charIndex]); + + // 현재 윈도우에서 변경해야 할 문자 수가 k를 초과하면 윈도우의 왼쪽을 이동 + // 현재 윈도우에서 변경해야 할 문자 수: 윈도우 크기 - 가장 많이 등장하는 문자의 빈도수 + // 윈도우 크기: right - left + 1 + if (right - left + 1 - maxCount > k) { + // 왼쪽 문자의 빈도수 감소 + const leftCharIndex = s.charCodeAt(left) - 65; + charCount[leftCharIndex]--; + left++; + } + + // 현재 윈도우 크기로 최대 길이 업데이트 + maxLength = Math.max(maxLength, right - left + 1); + } + + return maxLength; +}; diff --git a/palindromic-substrings/clara-shin.js b/palindromic-substrings/clara-shin.js new file mode 100644 index 000000000..58d1317c0 --- /dev/null +++ b/palindromic-substrings/clara-shin.js @@ -0,0 +1,46 @@ +/** + * 주어진 문자열에서 팰린드롬(앞뒤로 읽어도 같은 문자열)인 부분 문자열의 개수를 구하는 문제 + * + * 투 포인터(Two Pointers) vs. 중심확장(Center Expansion) + * 투포인터: 보통 정렬된 배열에서 두 요소의 합을 찾는 등에 사용 + * + * 중심확장이 더 적합한 이유: + * 팰린드롬의 특성(중심 대칭)을 직접적으로 활용 + * 불필요한 비교 최소화 + * 구현이 간단하고 이해하기 쉬움 + * + * 이번 문제는: Center Expansion 방법으로 접근 + * 모든 부분 문자열을 확인해야 함 + * 각 위치에서 팰린드롬 가능성을 체크해야 함 + * + * 시간 복잡도: O(n²) - 각 중심에서 최대 n번 확장 + * 공간 복잡도: O(1) - 추가 공간 거의 불필요 + */ +/** + * @param {string} s + * @return {number} + */ +var countSubstrings = function (s) { + if (!s || s.length === 0) return 0; // 빈 배열이면 빠른 리턴 + + let count = 0; + const n = s.length; + + // 중심 확장 함수 + function expandAroundCenter(left, right) { + while (left >= 0 && right < n && s[left] === s[right]) { + count++; + left--; + right++; + } + } + + for (let i = 0; i < n; i++) { + // 홀수 길이 팰린드롬 + expandAroundCenter(i, i); + // 짝수 길이 팰린드롬 + expandAroundCenter(i, i + 1); + } + + return count; +}; diff --git a/reverse-bits/clara-shin.js b/reverse-bits/clara-shin.js new file mode 100644 index 000000000..6fca7cf8c --- /dev/null +++ b/reverse-bits/clara-shin.js @@ -0,0 +1,31 @@ +/** + * 비트 반전 문제(32비트 무부호 정수, 32 bits unsigned integer) + * 비트 반전: 32비트 전체를 뒤집는 것(왼쪽과 오른쪽을 바꾸는 것) + * + * 접근 방법: + * 32비트 정수의 각 비트를 오른쪽에서 왼쪽으로 순회하며 확인 + * 각 비트를 왼쪽에서 오른쪽으로 반대로 위치시킴 + * 각 위치에 따라 결과값에 더함 + * + * 시간복잡도: O(1) + * 공간복잡도: O(1) + */ +/** + * @param {number} n - a positive integer + * @return {number} - a positive integer + */ +var reverseBits = function (n) { + let result = 0; + + for (let i = 0; i < 32; i++) { + // 결과에 현재 비트를 추가 (비트 OR 연산) + // 결과를 왼쪽으로 시프트한 후, n의 최하위 비트가 1이면 결과에 1을 더함 + result = (result << 1) | (n & 1); + + // n을 오른쪽으로 시프트 + n >>>= 1; + } + + // 부호 없는 정수로 변환 + return result >>> 0; +};