From 1b405e056057361e79a257e081dc5a790ee897e3 Mon Sep 17 00:00:00 2001 From: mmyeon Date: Mon, 3 Feb 2025 13:02:07 +0900 Subject: [PATCH 1/6] add solution : 141. Linked List Cycle --- linked-list-cycle/mmyeon.ts | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 linked-list-cycle/mmyeon.ts diff --git a/linked-list-cycle/mmyeon.ts b/linked-list-cycle/mmyeon.ts new file mode 100644 index 000000000..75621c36b --- /dev/null +++ b/linked-list-cycle/mmyeon.ts @@ -0,0 +1,58 @@ +class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} + +/** + * @link https://leetcode.com/problems/linked-list-cycle/description/ + * + * 접근 방법 : + * - 노드 순회하면서 방문한 노드에 저장 + * - 방문할 노드가 이미 방문한 노드에 있으면 순환 구조 이므로 true 리턴 + * + * 시간복잡도 : O(n) + * - 순환 이전 노드의 개수 n만큼 순회하면서 순환 여부 확인 + * + * 공간복잡도 : O(n) + * - visited set에 순환되기 이전 노드 n개 저장 + */ +function hasCycle(head: ListNode | null): boolean { + const visited = new Set(); + let current = head; + + while (current !== null) { + if (visited.has(current)) return true; + + visited.add(current); + current = current.next; + } + + return false; +} + +/* + * 접근 방법 : + * - 공간복잡도 O(1)로 풀이 + * - 사이클이 있으면 두 포인터가 결국 같은 노드를 가리키게 되므로 투 포인터 사용 + * - 사이클이 없는 경우를 위해서 tail노드의 null체크를 해야함 + * + * 공간복잡도 : O(1) + * - 추가 메모리없이 slow, fast 포인터만 사용하므로 O(1) + */ +function hasCycle(head: ListNode | null): boolean { + let slow = head; + let fast = head; + + while (fast !== null && fast.next !== null) { + slow = slow.next; + fast = fast.next.next; + + if (slow === fast) return true; + } + + return false; +} From 3045f60d90f3ca40a768bc8b49c3c1a475e088ad Mon Sep 17 00:00:00 2001 From: mmyeon Date: Mon, 3 Feb 2025 14:12:34 +0900 Subject: [PATCH 2/6] add solution : 153. Find Minimum in Rotated Sorted Array --- .../mmyeon.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 find-minimum-in-rotated-sorted-array/mmyeon.ts diff --git a/find-minimum-in-rotated-sorted-array/mmyeon.ts b/find-minimum-in-rotated-sorted-array/mmyeon.ts new file mode 100644 index 000000000..895c6f65f --- /dev/null +++ b/find-minimum-in-rotated-sorted-array/mmyeon.ts @@ -0,0 +1,28 @@ +/** + * @link https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/ + * + * 접근 방법 : + * - O(logn)으로 풀어야 하니까 이진 탐색 적용 + * - 배열의 start, end 인덱스를 활용해서 최소값이 있는 방향을 탐색 + * - nums[mid] > nums[end]이면, 최소값이 오른쪽에 있으니까 start를 mid+1로 이동 + * - 반대로 nums[mid] < nums[end] 이면, 최소값이 왼쪽에 있으니까 end를 mid로 이동 + * + * 시간복잡도 : O(logn) + * - 탐색 범위를 계속 절반으로 줄이니까 O(logn) + * + * 공간복잡도 : O(1) + * - 고정된 변수만 사용 + */ +function findMin(nums: number[]): number { + let start = 0, + end = nums.length - 1; + + while (start < end) { + let mid = Math.floor(start + (end - start) / 2); + + if (nums[mid] > nums[end]) start = mid + 1; + else end = mid; + } + + return nums[start]; +} From bfd79dda2d12d0bf2b241858c626db0c2dafef09 Mon Sep 17 00:00:00 2001 From: mmyeon Date: Wed, 5 Feb 2025 23:29:04 +0900 Subject: [PATCH 3/6] add solution : 417. Pacific Atlantic Water Flow --- pacific-atlantic-water-flow/mmyeon.ts | 76 +++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 pacific-atlantic-water-flow/mmyeon.ts diff --git a/pacific-atlantic-water-flow/mmyeon.ts b/pacific-atlantic-water-flow/mmyeon.ts new file mode 100644 index 000000000..dd4cac5e7 --- /dev/null +++ b/pacific-atlantic-water-flow/mmyeon.ts @@ -0,0 +1,76 @@ +/** + * @link https://leetcode.com/problems/pacific-atlantic-water-flow/description/ + * + * 접근 방법 : + * - pacific, atlantic를 동시에 도달하는 지점 찾기 위해서, 도달 여부 트래킹하는 2개의 visited 배열 사용한다. + * - 바다 경계에서만 DFS를 호출해서 방문 여부 체크한다. + * - 바다 경계에서 시작했기 때문에 DFS는 인접 셀의 높이가 같거나 높을 때만 호출되어야 한다. + * + * 시간복잡도 : O(m * n) + * - 각 셀마다 최대 4번 DFS가 호출될 수 있다. O(m * n) + * - 결과 순회할 때 m * n 만큼 순회한다. + * + * 공간복잡도 : O(m * n) + * - 2개의 visited 배열 사용한다. + * - 최악의 경우, m * n 모든 칸에서 DFS 호출된다. + */ + +const directions = [ + [-1, 0], + [1, 0], + [0, -1], + [0, 1], +]; + +function pacificAtlantic(heights: number[][]): number[][] { + const result: number[][] = []; + + const rows = heights.length; + const cols = heights[0].length; + + // 두 바다 도달하는 지점 트래킹 하기 위한 배열 + const pacificVisited = Array.from({ length: rows }, () => + Array(cols).fill(false) + ); + const atlanticVisited = Array.from({ length: rows }, () => + Array(cols).fill(false) + ); + + const dfs = (row: number, col: number, visited: boolean[][]) => { + if (visited[row][col]) return; + // 방문 지점 기록 + visited[row][col] = true; + + for (const [x, y] of directions) { + const newRow = row + x; + const newCol = col + y; + + // 새로운 위치가 경계안에 있고, 현재 높이보다 같거나 높을 때만 DFS 호출 + if ( + 0 <= newRow && + newRow < rows && + 0 <= newCol && + newCol < cols && + heights[newRow][newCol] >= heights[row][col] + ) + dfs(newRow, newCol, visited); + } + }; + + // pacific 경계에서 DFS 호출 (첫 번쨰 열, 첫 번째 행) + for (let row = 0; row < rows; row++) dfs(row, 0, pacificVisited); + for (let col = 0; col < cols; col++) dfs(0, col, pacificVisited); + + // atlantic 경계에서 DFS 호출 (마지막 열, 마지막 행) + for (let row = 0; row < rows; row++) dfs(row, cols - 1, atlanticVisited); + for (let col = 0; col < cols; col++) dfs(rows - 1, col, atlanticVisited); + + for (let row = 0; row < rows; row++) { + for (let col = 0; col < cols; col++) { + if (pacificVisited[row][col] && atlanticVisited[row][col]) + result.push([row, col]); + } + } + + return result; +} From 58fd46f60bbbfed4a075c47efd64a422dedb695b Mon Sep 17 00:00:00 2001 From: mmyeon Date: Fri, 7 Feb 2025 17:56:45 +0900 Subject: [PATCH 4/6] add solution : 76. Minimum Window Substring --- minimum-window-substring/mmyeon.ts | 69 ++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 minimum-window-substring/mmyeon.ts diff --git a/minimum-window-substring/mmyeon.ts b/minimum-window-substring/mmyeon.ts new file mode 100644 index 000000000..2cdcc27b4 --- /dev/null +++ b/minimum-window-substring/mmyeon.ts @@ -0,0 +1,69 @@ +/** + * @link https://leetcode.com/problems/minimum-window-substring/description/ + * 접근 방법 : 2개의 포인터 활용해서 슬라이딩 윈도우 방식 사용 + * - t의 문자를 맵에 저장해서 개수 기록 + * - right 포인터로 t의 모든 문자 포함할 때까지 윈도우 확장 + * - 모든 문자 포함하면, left 포인터로 최소 윈도우 찾을 때까지 윈도우 축소 + * - '확장 => 축소 => 최소 윈도우 업데이트' 이 과정을 반복 + * + * 시간복잡도 : O(n) + * - n은 s의 길이, 각 문자 최대 2회 방문 (확장 + 축소) + * + * 공간복잡도 : O(n) + * - 최악의 경우, 윈도우에 s의 모든 문자가 저장됨 + */ +function minWindow(s: string, t: string): string { + const targetCharCount = new Map(); + + // t의 문자 개수 카운트 + for (const char of t) { + targetCharCount.set(char, (targetCharCount.get(char) ?? 0) + 1); + } + + const requiredUniqueChars = targetCharCount.size; + let matchedUniqueChars = 0; + const windowCharCount = new Map(); + + let minWindow = ""; + let minWindowLength = Infinity; + + let left = 0, + right = 0; + + while (right < s.length) { + const char = s[right]; + windowCharCount.set(char, (windowCharCount.get(char) ?? 0) + 1); + + // t에 속하는 문자이면서, 문자 개수가 같은 경우 + if ( + targetCharCount.has(char) && + targetCharCount.get(char) === windowCharCount.get(char) + ) + matchedUniqueChars++; + + while (matchedUniqueChars === requiredUniqueChars) { + const windowLength = right - left + 1; + + // 최소 윈도우 업데이트 + if (windowLength < minWindowLength) { + minWindowLength = windowLength; + minWindow = s.substring(left, right + 1); + } + + const leftChar = s[left]; + windowCharCount.set(leftChar, windowCharCount.get(leftChar)! - 1); + + //축소로 윈도우 내의 t문자가 감소했으면 matchedUniqueChars 감소 + if (windowCharCount.get(leftChar)! < targetCharCount.get(leftChar)!) + matchedUniqueChars--; + + // 윈도우 축소 + left++; + } + + // 윈도우 확장 + right++; + } + + return minWindow; +} From 0bd99fe4f036287675572386ed61c658f45d3907 Mon Sep 17 00:00:00 2001 From: mmyeon Date: Sat, 8 Feb 2025 23:48:51 +0900 Subject: [PATCH 5/6] add solution : 152. Maximum Product Subarray --- maximum-product-subarray/mmyeon.ts | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 maximum-product-subarray/mmyeon.ts diff --git a/maximum-product-subarray/mmyeon.ts b/maximum-product-subarray/mmyeon.ts new file mode 100644 index 000000000..1f44842cb --- /dev/null +++ b/maximum-product-subarray/mmyeon.ts @@ -0,0 +1,31 @@ +/** + * @link https://leetcode.com/problems/maximum-product-subarray/ + * + * 접근 방법 : + * - 음수가 짝수번 나오면 최대값이 될 수 있으니까 최소곱, 최대곱을 모두 업데이트 + * - 현재 값이 단독으로 최소, 최대일 수 있으니까 현재값, 최소곱 * 현재값, 최대곱 * 현재값을 비교 + * + * 시간복잡도 : O(n) + * - nums 1회 순회 + * + * 공간복잡도 : O(1) + * - 고정된 변수만 사용 + */ +function maxProduct(nums: number[]): number { + let currentMin = nums[0], + currentMax = nums[0], + maxSoFar = nums[0]; + + for (let i = 1; i < nums.length; i++) { + const num = nums[i]; + const minCandidate = currentMin * num; + const maxCandidate = currentMax * num; + + currentMin = Math.min(num, minCandidate, maxCandidate); + currentMax = Math.max(num, minCandidate, maxCandidate); + + maxSoFar = Math.max(currentMax, maxSoFar); + } + + return maxSoFar; +} From 0d0395b531cffc88d91427a1cd3249a527b2f66b Mon Sep 17 00:00:00 2001 From: mmyeon Date: Sat, 8 Feb 2025 23:51:47 +0900 Subject: [PATCH 6/6] add time complexity --- linked-list-cycle/mmyeon.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/linked-list-cycle/mmyeon.ts b/linked-list-cycle/mmyeon.ts index 75621c36b..2f9236925 100644 --- a/linked-list-cycle/mmyeon.ts +++ b/linked-list-cycle/mmyeon.ts @@ -40,6 +40,9 @@ function hasCycle(head: ListNode | null): boolean { * - 사이클이 있으면 두 포인터가 결국 같은 노드를 가리키게 되므로 투 포인터 사용 * - 사이클이 없는 경우를 위해서 tail노드의 null체크를 해야함 * + * 시간복잡도 : O(n) + * - 순환 이전 노드의 개수 n만큼 순회하면서 순환 여부 확인 + * * 공간복잡도 : O(1) * - 추가 메모리없이 slow, fast 포인터만 사용하므로 O(1) */