diff --git a/course-schedule/Yjason-K.ts b/course-schedule/Yjason-K.ts new file mode 100644 index 000000000..12fc25898 --- /dev/null +++ b/course-schedule/Yjason-K.ts @@ -0,0 +1,61 @@ + +/** + * 선수 과목과 이수 과목 관계를 확인하여 모든 과목을 이수할 수 있는지 여부 확인. + * @param {number} numCourses - 전체 과목 수 + * @param {number[][]} prerequisites - 선수 과목과 이수 과목 관계 정보 + * @returns {boolean} - 모든 과목을 수강할 수 있는지 여부 + * + * 시간 복잡도: O(V + E) + * - 그래프를 생성하는 과정에서 O(E), + * - bfs O(V + E), + * + * 공간 복잡도: O(V + E) + * - 각 과목에 대한 선수 과목 저장에 O(V + E), + * - prereqCount 저장에 O(V), + * - 큐(queue) 저장에 O(V), + */ +function canFinish(numCourses: number, prerequisites: number[][]): boolean { + // 각 과목의 선수 과목 정보를 저장하는 리스트 + const graph: number[][] = Array.from({ length: numCourses }, () => []); + // 각 과목의 선수 과목 수 를 저장하는 배열 + const prereqCount: number[] = new Array(numCourses).fill(0); + + // 그래프 및 선수 과목 개수 초기화 + for (const [course, prereq] of prerequisites) { + graph[prereq].push(course); // 선수 과목을 들어야 수강 가능한 과목 추가 + prereqCount[course]++; // 해당 과목의 선수 과목 개수 증가 + } + + // 선수 과목이 없는 과목들을 저장할 큐 + const queue: number[] = []; + + // 선수 과목이 없는 과목들을 큐에 추가 (진입 차수가 0인 노드) + for (let i = 0; i < numCourses; i++) { + if (prereqCount[i] === 0) { + queue.push(i); + } + } + + // 수강한 과목 수 + let completedCourses = 0; + + // 선수 과목이 없는 과목을 사용해서 다음 과목을 수강 + while (queue.length > 0) { + const current = queue.shift()!; + completedCourses++; // 과목 수강 완료 + + // 현재 과목을 선수 과목으로 하는 다른 과목들의 선수 과목 개수 감소 + for (const nextCourse of graph[current]) { + prereqCount[nextCourse]--; + + // 선수 과목 개수가 0이 되면 큐에 추가 (더 이상 기다릴 필요 없음) + if (prereqCount[nextCourse] === 0) { + queue.push(nextCourse); + } + } + } + + // 모든 과목을 수강 수 있는지 확인 + return completedCourses === numCourses; +} + diff --git a/find-minimum-in-rotated-sorted-array/Yjason-K.ts b/find-minimum-in-rotated-sorted-array/Yjason-K.ts new file mode 100644 index 000000000..beb4eb5ae --- /dev/null +++ b/find-minimum-in-rotated-sorted-array/Yjason-K.ts @@ -0,0 +1,31 @@ +/** + * 배열에서 가장 작은 수 찾기 ( 제약 : 시간 복잡도 O(log n) ) + * @param {number[]} nums 회전된 수 배열 + * + * 시간 복잡되: O(log n) + * - 이분 탐색을 사용하여 최소값을 탐색 + * + * 공간 복잡도: O(1) + */ +function findMin(nums: number[]): number { + let left = 0, right = nums.length - 1; + + while (left <= right) { + let mid = Math.floor((left + right) / 2); + + // 정렬이 망가진 경우 + if (nums[mid] < nums[mid-1]) return nums[mid]; + + // left, right 범위 줄여나가기 + if (nums[0] < nums[mid]){ + left = mid + 1; + } else { + right = mid - 1; + } + } + + // 탐색 후에도 찾지 못한 경우 회전되지 않은 경우 + return nums[0]; + +} + diff --git a/invert-binary-tree/Yjason-K.ts b/invert-binary-tree/Yjason-K.ts new file mode 100644 index 000000000..6c4b9a7b1 --- /dev/null +++ b/invert-binary-tree/Yjason-K.ts @@ -0,0 +1,26 @@ +/** + * 이진 트리를 반전시키는 함수 + * @param {TreeNode | null} root - 반전할 이진 트리의 루트 노드 + * @returns {TreeNode | null} - 반전된 이진 트리의 루트 노드 + * + * 시간 복잡도: O(n) + * - 트리의 모든 노드를 한 번씩 방문해야 하므로 선형 시간 복잡도를 가짐 + * 공간 복잡도: O(h) + * - 재귀 호출에 의해 최대 트리의 높이(h)만큼의 호출 스택이 필요 + */ +function invertTree(root: TreeNode | null): TreeNode | null { + // 루트가 null이면 null 반환 + if (!root) return null; + + // 왼쪽과 오른쪽 서브트리를 재귀적으로 반전 + const left = invertTree(root.left); + const right = invertTree(root.right); + + // 현재 노드의 왼쪽과 오른쪽 서브트리를 교환 + root.left = right; + root.right = left; + + // 반전된 루트 노드를 반환 + return root; +} + diff --git a/jump-game/Yjason-K.ts b/jump-game/Yjason-K.ts new file mode 100644 index 000000000..234ee95e7 --- /dev/null +++ b/jump-game/Yjason-K.ts @@ -0,0 +1,30 @@ +/** + * 배열의 각 인덱스에서 가능한 점프를 통해 마지막 인덱스에 도달할 수 있는지 확인하는 문제 + * @param {number[]} nums - 각 인덱스에서 이동할 수 있는 최대 거리 + * @returns {boolean} - 마지막 인덱스에 도달할 수 있으면 true, 아니면 false + * + * 시간 복잡도: O(2^n) + * - DFS 기반의 탐색이므로 최악의 경우 모든 경우 탐색 + * + * 공간 복잡도: O(n) (재귀 호출 스택과 방문 배열 사용) + * - `visited` 배열이 nums.length 크기를 차지 + */ +function canJump(nums: number[]): boolean { + const visited = new Array(nums.length).fill(false); + + const dfs = (idx: number): boolean => { + if (idx >= nums.length - 1) return true; // 마지막 인덱스 이상 도달하면 성공 + if (visited[idx]) return false; // 이미 방문한 경우 중복 방문 방지 + + visited[idx] = true; // 방문 처리 + + for (let i = 1; i <= nums[idx]; i++) { // 현재 위치에서 가능한 모든 점프 탐색 + if (dfs(idx + i)) return true; + } + + return false; + } + + return dfs(0); // 0번 인덱스에서 시작 +} + diff --git a/linked-list-cycle/Yjason-K.ts b/linked-list-cycle/Yjason-K.ts new file mode 100644 index 000000000..31eb00430 --- /dev/null +++ b/linked-list-cycle/Yjason-K.ts @@ -0,0 +1,37 @@ +/** + * Definition for singly-linked list. + * 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) + * } + * } + */ + +/** + * 연결 리스트인가 순환하는지 여부 확인 + * @param {ListNode} head - ListNode + * @returns {boolean} - 순환 여부 + * + * 시간 복잡도: O(n) + * + * 공간 복잡도: O(n) + * - 노드의 개수만큼 Set에 저장 + */ +function hasCycle(head: ListNode | null): boolean { + const set = new Set(); + + while (head) { + if (set.has(head)) { + return true; + } + + set.add(head); + head = head.next; + } + + return false; +} + diff --git a/maximum-product-subarray/Yjason-K.ts b/maximum-product-subarray/Yjason-K.ts new file mode 100644 index 000000000..9eed79489 --- /dev/null +++ b/maximum-product-subarray/Yjason-K.ts @@ -0,0 +1,32 @@ +/** + * subArray 중 최대 곱을 구하는 함수 + * @param {number[]} nums - 숫자 배열 + * @returns {number} - subArray 중 최대 곱 + * + * @description 음수의 곱이 존재 할 수 있기 때문에, 최대 값과 최소값을 갱신하며, 결과 값을 갱신. + * + * 시간 복잡도 : O(n) + * - nums 배열 1회 순회 + * + * 공간 복잡도 : O(1) + */ +function maxProduct(nums: number[]): number { + let max = nums[0]; + let min = nums[0]; + let result = nums[0]; + + // 첫 번째 요소를 제외한 모든 요소를 탐색 + for (let i = 1; i < nums.length; i++) { + let current = nums[i] + + // 현재 값, 이전 최대 곱과의 곱, 이전 최소 곱과의 곱 중 최대/최소 갱신 + const cases = [current * max, current * min, current]; + + max = Math.max(...cases); + min = Math.min(...cases); + result = Math.max(result, max); + } + + return result; +} + diff --git a/pacific-atlantic-water-flow/Yjason-K.ts b/pacific-atlantic-water-flow/Yjason-K.ts new file mode 100644 index 000000000..81b76aacc --- /dev/null +++ b/pacific-atlantic-water-flow/Yjason-K.ts @@ -0,0 +1,76 @@ +/** + * 깊이 우선 탐색(DFS)을 사용하여 특정 바다에서 올라갈 수 있는 좌표을 저장 + * @param i 현재 위치의 행 (row) + * @param j 현재 위치의 열 (column) + * @param visited 방문한 좌표를 저장하는 Set (바다에서 도달할 수 있는 위치를 저장) + * @param heights 높이 정보가 담긴 2차원 배열 + * + * 시간 복잡도: O(m × n) + * - 각 셀은 최대 한 번 방문하며, 총 m × n개의 셀을 탐색함 + * + * 공간 복잡도: O(m × n) + * - `visited` Set에 최대 m × n개의 좌표를 저장 가능 + * - 재귀 호출 스택의 깊이는 O(m + n) (최악의 경우 가장 긴 경로를 따라 탐색) + */ +function dfs(i: number, j: number, visited: Set, heights: number[][]) { + if (visited.has(`${i},${j}`)) return; + + visited.add(`${i},${j}`); + + for (const [di, dj] of [[-1, 0], [1, 0], [0, -1], [0, 1]]) { + const newI = i + di; + const newJ = j + dj; + + if ( + newI >= 0 && newI < heights.length && + newJ >= 0 && newJ < heights[0].length && + heights[newI][newJ] >= heights[i][j] + ) { + dfs(newI, newJ, visited, heights); + } + } +}; + +/** + * 두 바다 모두 도달할 수 있는 좌표를 찾는 함수 + * + * @param heights 2차원 배열로 이루어진 지형의 높이 정보 + * @returns 두 바다 모두 도달할 수 있는 좌표 배열 + * + * 시간 복잡도: O(m × n) + * - 태평양 및 대서양에서 각각 DFS 수행 → O(m × n) + * - 결과를 찾는 이중 루프 → O(m × n) + * + * 공간 복잡도: O(m × n) + * - `pacificSet`과 `atlanticSet`에 최대 O(m × n)개의 좌표 저장 + * + */ +function pacificAtlantic(heights: number[][]): number[][] { + if (!heights || heights.length === 0 || heights[0].length === 0) return []; + + const rows = heights.length; + const cols = heights[0].length; + + const pacificSet = new Set(); + const atlanticSet = new Set(); + + // 태평양(왼쪽, 위쪽)에서 출발하는 DFS + for (let i = 0; i < rows; i++) dfs(i, 0, pacificSet, heights); + for (let j = 0; j < cols; j++) dfs(0, j, pacificSet, heights); + + // 대서양(오른쪽, 아래쪽)에서 출발하는 DFS + for (let i = 0; i < rows; i++) dfs(i, cols - 1, atlanticSet, heights); + for (let j = 0; j < cols; j++) dfs(rows - 1, j, atlanticSet, heights); + + const result: number[][] = []; + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + if (pacificSet.has(`${i},${j}`) && atlanticSet.has(`${i},${j}`)) { + result.push([i, j]); + } + } + } + + return result; +}; + diff --git a/search-in-rotated-sorted-array/Yjason-K.ts b/search-in-rotated-sorted-array/Yjason-K.ts new file mode 100644 index 000000000..0dbcb1b7c --- /dev/null +++ b/search-in-rotated-sorted-array/Yjason-K.ts @@ -0,0 +1,55 @@ +// similar problem to #245 Find Minimum In Rotated Sorted Array +/** + * 회정된 배열에서 target의 index를 찾는 문제 + * @param {number[]} nums - 회전된 배열 + * @param target - 찾는 수 + * @returns {number} - target의 index + * + * 시간 복잡도: O(log n) + * - 이분 탐색을 사용하여 최적의 탐색을 수행 + * + * 공간 복잡도: O(1) + * - 추가적인 공간 사용 없이 포인터만 이용하여 탐색 + */ +function search(nums: number[], target: number): number { + let left = 0, right = nums.length - 1; + + while (left <= right) { + let mid = Math.floor((left + right) / 2); + + if (nums[mid] === target) return mid; + + // mid를 기준으로 왼쪽 부분이 정렬된 경우 + if (nums[left] <= nums[mid]) { + /** + * 왼쪽 부분이 오름차순 정렬되어 있다면: + * - nums[left] ~ nums[mid] 범위는 정렬되어 있음 + * - target이 이 범위 내에 있다면, right를 줄여서 탐색 + * - 아니라면 target은 오른쪽에 있으므로 left를 증가시켜 탐색 + */ + if (nums[left] <= target && target < nums[mid]) { + right = mid - 1; // 왼쪽 범위에서 탐색 + } else { + left = mid + 1; // 오른쪽 범위에서 탐색 + } + } + // mid를 기준으로 오른쪽 부분이 정렬된 경우 + else { + /** + * 오른쪽 부분이 오름차순 정렬되어 있다면: + * - nums[mid] ~ nums[right] 범위는 정렬되어 있음 + * - target이 이 범위 내에 있다면, left를 늘려서 탐색 + * - 아니라면 target은 왼쪽에 있으므로 right를 감소시켜 탐색 + */ + if (nums[mid] < target && target <= nums[right]) { + left = mid + 1; // 오른쪽 범위에서 탐색 + } else { + right = mid - 1; // 왼쪽 범위에서 탐색 + } + } + } + + // target을 찾지 못한 경우 + return -1; +} +