diff --git a/clone-graph/mmyeon.ts b/clone-graph/mmyeon.ts new file mode 100644 index 000000000..5a18d0f8d --- /dev/null +++ b/clone-graph/mmyeon.ts @@ -0,0 +1,48 @@ +class _Node { + val: number; + neighbors: _Node[]; + constructor(val?: number, neighbors?: _Node[]) { + this.val = val === undefined ? 0 : val; + this.neighbors = neighbors === undefined ? [] : neighbors; + } +} + +/** + * @link https://leetcode.com/problems/clone-graph/description/ + * + * 접근 방법 : + * - 엣지 케이스 : 주어진 노드가 null이면 그대로 리턴 + * - 이미 방문한 노드인지 확인 : 방문한 노드인 경우, 저장된 복사 노드 리턴해서 중복 생성 방지 + * - 새로운 노드 클론하고 visited 맵에 저장 + * - 해당 노드의 이웃 노드도 순회하면서 복제 + * - 클론된 노드 리턴 + * + * 시간복잡도 : O(n + e) + * - n은 노드의 개수, e는 노드 연결하는 엣지의 개수 + * - 모든 노드 순회하면서 각 노드의 이웃 탐색 + * + * 공간복잡도 : O(n) + * - 모든 노드를 클론해서 visited 맵에 저장되므로 O(n) + * - 그래프가 선형 구조인 최악의 경우, 재귀 호출 스택이 O(n) + */ +function cloneGraph(node: _Node | null): _Node | null { + if (!node) return null; + const visited = new Map(); + + const cloneNode = (node: _Node): _Node => { + if (visited.has(node.val)) return visited.get(node.val) as _Node; + + const clonedNode = new _Node(node.val); + visited.set(node.val, clonedNode); + + for (const neighbor of node.neighbors) { + const clonedNeighbor = cloneNode(neighbor); + + clonedNode.neighbors.push(clonedNeighbor); + } + + return clonedNode; + }; + + return cloneNode(node); +} diff --git a/longest-common-subsequence/mmyeon.ts b/longest-common-subsequence/mmyeon.ts new file mode 100644 index 000000000..9d3ca967e --- /dev/null +++ b/longest-common-subsequence/mmyeon.ts @@ -0,0 +1,31 @@ +/** + * @link https://leetcode.com/problems/longest-common-subsequence/description/ + * + * 접근 방법 : + * - LCS 길이 담을 DP 배열 선언 + * - 문자 순회하면서 같은 문자인 경우, 이전 값 + 1 로 업데이트 + * - 문자 다른 경우, 이전 값 그대로 유지 + * + * 시간복잡도 : O(m * n) + * - 두 문자열 길이 크기만큼 이중 반복문 실행 + * + * 공간복잡도 : O(m * n) + * - 두 문자 길이 크기만큼 DP 배열에 저장 + */ +function longestCommonSubsequence(text1: string, text2: string): number { + const m = text1.length, + n = text2.length; + + const dp = Array.from({ length: m + 1 }, () => 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]) { + 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[m][n]; +} diff --git a/longest-repeating-character-replacement/mmyeon.ts b/longest-repeating-character-replacement/mmyeon.ts new file mode 100644 index 000000000..b339096c1 --- /dev/null +++ b/longest-repeating-character-replacement/mmyeon.ts @@ -0,0 +1,39 @@ +/** + * @link https://leetcode.com/problems/longest-repeating-character-replacement/ + * + * 접근 방법 : + * - 문자열 순회하면서, 현재 윈도우 내 문자의 빈도수 저장 + * - 윈도우 축소 조건 : 윈도우 크기 - 윈도우 내 최다 등장 문자의 개수 > k + * => k보다 다른 문자의 개수가 많은 경우, left 포인터 이동해서 윈도우 크기 줄이기 + * - 윈도우 크기 조절하면서 최대 길이 업데이트 + * + * 시간복잡도 : O(n) + * - 문자 n개만큼 1회 순회하면서 윈도우 크기 조절 + * + * 공간복잡도 : O(1) + * - 대문자의 개수(26개)만큼 map에 저장 + */ +function characterReplacement(s: string, k: number): number { + const map = new Map(); + let maxFrequency = 0, + maxLength = 0, + left = 0; + + for (let right = 0; right < s.length; right++) { + const rightPositionChar = s[right]; + // 문자의 빈도수 map에 저장 + map.set(rightPositionChar, (map.get(rightPositionChar) ?? 0) + 1); + maxFrequency = Math.max(maxFrequency, map.get(rightPositionChar)!); + + // 윈도우 축소해야 되는 경우 - k보다 다른 문자의 개수가 많은 경우 + if (right - left + 1 - maxFrequency > k) { + map.set(s[left], map.get(s[left])! - 1); + left++; + } + + // 최대 길이 업데이트 + maxLength = Math.max(maxLength, right - left + 1); + } + + return maxLength; +} diff --git a/number-of-1-bits/mmyeon.ts b/number-of-1-bits/mmyeon.ts new file mode 100644 index 000000000..b3f87fa03 --- /dev/null +++ b/number-of-1-bits/mmyeon.ts @@ -0,0 +1,38 @@ +/** + * @link https://leetcode.com/problems/number-of-1-bits/description/ + * + * 접근 방법 : + * - n을 2로 나누면서 나머지가 1인 경우 카운트를 업데이트한다. + * + * 시간복잡도 : O(logn) + * - 숫자의 비트 길이만큼 반복 + * + * 공간복잡도 : O(1) + * - 고정된 변수만 사용 + */ +function hammingWeight(n: number): number { + let bitCount = 0; + while (n >= 1) { + bitCount += n % 2; + + n = Math.floor(n / 2); + } + + return bitCount; +} + +/** + * 접근 방법 : 비트 연산자 활용 + * - n & 1 : n의 마지막 비트가 1인지 확인하여 bitCount 업데이트 + * - n >>>= 1 : 오른쪽 시프트로 n을 1비트씩 이동 + */ +function hammingWeight(n: number): number { + let bitCount = 0; + while (n >= 1) { + bitCount += n & 1; + + n >>>= 1; + } + + return bitCount; +} diff --git a/sum-of-two-integers/mmyeon.ts b/sum-of-two-integers/mmyeon.ts new file mode 100644 index 000000000..cf809e8f6 --- /dev/null +++ b/sum-of-two-integers/mmyeon.ts @@ -0,0 +1,29 @@ +/** + *@link https://leetcode.com/problems/sum-of-two-integers/description/ + * + * 접근 방법 : + * - 비트 AND 연산자(&) 사용해서 자리 올림이 필요한 비트 계산하고 왼쪽 시프트(<<)로 자리 올림값을 다음 자리로 이동 + * - 비트 XOR 연산자(^) 사용해서 자리 올림 제외한 자리합 계산하고 a값 업데이트 + * - 자리 올림값이 0이 될 때까지 자리 올림 반복 + * - 자리 올림 없으면 최종합이 저장된 a 리턴 + * + * + * 시간복잡도 : O(k) + * - k는 숫자의 비트 수, 최대 k번 반복 + * + * 공간복잡도 : O(1) + * - 고정된 변수만 사용 + * + */ + +function getSum(a: number, b: number): number { + while (b) { + // 자리 올림 계산 + const carry = (a & b) << 1; + // 자리합 업데이트 (같은 비트 = 0, 다른 비트 = 1) + a ^= b; + b = carry; + } + + return a; +}