diff --git a/coin-change/mmyeon.ts b/coin-change/mmyeon.ts new file mode 100644 index 000000000..7e9bc0d1f --- /dev/null +++ b/coin-change/mmyeon.ts @@ -0,0 +1,27 @@ +/** + * + * 접근 방법 : + * - 동전 최소 개수 구하는 문제니까 DP로 풀기 + * - 코인마다 순회하면서 동전 개수 최소값 구하기 + * - 기존값과 코인을 뺀 값 + 1 중 작은 값으로 업데이트 + * + * 시간복잡도 : O(n * m) + * - 코인 개수 n만큼 순회 + * - 각 코인마다 amount 값(m) 될 때까지 순회 + * + * 공간복잡도 : O(m) + * - amount 만큼 dp 배열 생성 + * + */ +function coinChange(coins: number[], amount: number): number { + const dp = Array(amount + 1).fill(Number.MAX_VALUE); + dp[0] = 0; + + for (const coin of coins) { + for (let i = coin; i <= amount; i++) { + dp[i] = Math.min(dp[i], dp[i - coin] + 1); + } + } + + return dp[amount] === Number.MAX_VALUE ? -1 : dp[amount]; +} diff --git a/merge-two-sorted-lists/mmyeon.ts b/merge-two-sorted-lists/mmyeon.ts new file mode 100644 index 000000000..167aa3981 --- /dev/null +++ b/merge-two-sorted-lists/mmyeon.ts @@ -0,0 +1,46 @@ +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; + } +} + +/** + * + * 접근 방법 : + * - 2개의 정렬된 링크드 리스트가 주어지니까 각 리스트 값 비교하면서 작은 값을 새로운 링크드 리스트에 추가 + * - 링크드 리스트 head에 접근해야하니까 더미노드와 포인터 변수 분리해서 사용 + * - 포인터 변수 사용해서 노드 연결하기 + * - 두 링크드 리스트가 있는 동안 반복하고, 한 쪽이 끝나면 나머지 노드를 그대로 새로운 링크드 리스트에 추가 + * + * 시간복잡도 : O(n+k) + * - n은 list1 길이, k는 list2 길이 => 두 리스트 모두 반복하니까 O(n+k) + * + * 공간복잡도 : O(1) + * - 기존 노드 연결해서 재사용하니까 O(1) + */ + +function mergeTwoLists( + list1: ListNode | null, + list2: ListNode | null +): ListNode | null { + const dummyNode = new ListNode(); + let current = dummyNode; + + while (list1 !== null && list2 !== null) { + if (list1.val <= list2.val) { + current.next = list1; + list1 = list1.next; + current = current.next; + } else { + current.next = list2; + list2 = list2.next; + current = current.next; + } + } + current.next = list1 !== null ? list1 : list2; + + return dummyNode.next; +} diff --git a/missing-number/mmyeon.ts b/missing-number/mmyeon.ts new file mode 100644 index 000000000..e7c4b7fac --- /dev/null +++ b/missing-number/mmyeon.ts @@ -0,0 +1,36 @@ +/** + * + * 접근 방법 : + * - 연속되는 숫자 중 빠진 숫자 찾는 거니까 배열 인덱스를 활용하자. + * - 배열 요소 순회하면서 map에 요소를 키로 넣고, nums 길이만큼 순회하면서 map에 값 있는지 체크 + * - 없으면 해당 인덱스 리턴하고, 순회 끝나면 nums 길이 리턴 + * + * 시간복잡도 : O(n) + * - nums 길이만큼 map에 요소 넣고, map에 요소 있는지 체크하니까 O(n) + * + * 공간복잡도 : O(n) + * - map에 nums 길이만큼 저장하니까 O(n) + */ + +function missingNumber(nums: number[]): number { + const set = new Set(nums); + + for (let i = 0; i < nums.length; i++) { + if (!set.has(i)) return i; + } + + return nums.length; +} + +// 공간복잡도 O(1) 개선 방법 +// - 0부터 n까지의 합 구하고, 실제 nums 요소 값 빼서 빠진 숫자 구함 +function missingNumber(nums: number[]): number { + let sum = 0; + + for (let i = 0; i <= nums.length; i++) { + sum += i; + if (i < nums.length) sum -= nums[i]; + } + + return sum; +} diff --git a/palindromic-substrings/mmyeon.ts b/palindromic-substrings/mmyeon.ts new file mode 100644 index 000000000..bacf48a2b --- /dev/null +++ b/palindromic-substrings/mmyeon.ts @@ -0,0 +1,37 @@ +/** + * + * 접근 방법 : + * - 각 문자열에서 회문 조건 충족하는 경우 중심을 기준으로 확장해나가기 위해 투 포인터 사용 + * - 문자가 같고 범위 내에 있는 경우 확장해나가면서 횟수 업데이트 + * - 홀수 회문과 다르게 짝수 회문은 중심을 2문자에서 시작되어야 하니까 인덱스 별도 처리 + * + * 시간복잡도 : O(n^2) + * - 문자열 길이가 n일 때, for문에서 각 문자마다 최대 문자열 길이까지 비교하니까 O(n^2) + * + * 공간복잡도 : O(1) + * + */ + +function countPalindromes(s: string, left: number, right: number): number { + let count = 0; + + while (0 <= left && right < s.length && s[left] === s[right]) { + count++; + left--; + right++; + } + + return count; +} + +function countSubstrings(s: string): number { + let count = 0; + + for (let i = 0; i < s.length; i++) { + // 홀수 회문 카운트 + count += countPalindromes(s, i, i); + // 짝수 회문 카운트 + count += countPalindromes(s, i, i + 1); + } + return count; +} diff --git a/word-search/mmyeon.ts b/word-search/mmyeon.ts new file mode 100644 index 000000000..f43907ae2 --- /dev/null +++ b/word-search/mmyeon.ts @@ -0,0 +1,60 @@ +/** + * + * 접근 방법 : + * 1. 행렬 순회하며 word와 첫 번째 문자가 같은지 체크 + * 2. 같으면 DFS(재귀)로 네 방향(상하좌우)을 탐색한다. + * - 현재 위치가 유효한지 체크 = 범위 안인가, 문자가 같은가 + * - 단어 다 찾아서 index가 단어 길이와 같은지 체크 + * 3. 이미 방문한 노드 제외하기 위해서 네 방향 체크하기 전에 방문 여부 표시하기 + * 4. 4방향으로 문자 체크하기 + * 5. 재귀 호출하는 동안 찾지 못한 경우 방문 여부 초기화하기 (backtracking) + * + * 시간복잡도 : O(N * M * 4^L) + * - L는 word의 길이, word 길이만큼 네 방향 체크하니까 O(4^L) + * 공간복잡도 : O(L) + * + * - L는 word의 길이, 찾으려는 단어 길이만큼 재귀 호출되니까 O(L) + * + */ + +function exist(board: string[][], word: string): boolean { + const rows = board.length; + const cols = board[0].length; + + const dfs = (x: number, y: number, index: number): boolean => { + // 종료조건 : 문자를 다 찾은 경우 + if (index === word.length) return true; + + // 범위를 벗어나거나 이미 방문했거나 문자가 다른 경우 + if (x < 0 || y < 0 || x >= rows || y >= cols || board[x][y] !== word[index]) + return false; + + // 방문 표시 + const temp = board[x][y]; + board[x][y] = "#"; + + // 4 방향 + const directions = [ + [1, 0], + [0, 1], + [-1, 0], + [0, -1], + ]; + + for (const [dx, dy] of directions) { + if (dfs(x + dx, y + dy, index + 1)) return true; + } + + // 백트래킹 + board[x][y] = temp; + return false; + }; + + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + if (word[0] === board[i][j] && dfs(i, j, 0)) return true; + } + } + + return false; +}