diff --git a/3sum/mmyeon.ts b/3sum/mmyeon.ts new file mode 100644 index 000000000..70892775e --- /dev/null +++ b/3sum/mmyeon.ts @@ -0,0 +1,60 @@ +/** + * + * 접근 방법 : + * - 3개의 숫자를 더한 값이 0이 되는 숫자의 조합 찾는 문제 + * - for문과 투 포인터 사용해서 숫자 조합 찾도록 접근 + * - 투 포인터 이동 조건을 정하기 위해서 배열 오름차순으로 정렬 + * - 합이 0보다 크면 오른쪽 포인터 1 감소하고, 0보다 작으면 왼쪽 포인터 1 증가 + * - 합이 0인 경우에는, 결과값에 조합 저장하고, 포인터 2개 모두 이동시키기 + * - 조합 중복 제거하기 위해서, 첫 번째 숫자와 두 세 번째 숫자 지정할 때 값 같은지 체크해서 같으면 다음 숫자로 넘어가도록 처리 + * + * 시간복잡도 : O(n^2) + * - 배열 정렬 O(nlogn) + * - for문 순회하고 내부에서 while문으로 요소 모두 순회하니까 O(n^2) + * + * 공간복잡도 : + * - 포인터 변수, sum 변수만 사용해서 O(1) + * + * 배운 점 : + * - Set을 활용해서 마지막에 중복 제거하려고 했는데 참조값이라서 원하는대로 동작 안했다. 결과값에 추가하고 중복 제거하는 것보다 추가하기 이전에 중복 제거하는 방식 고려해보기. + */ + +function threeSum(nums: number[]): number[][] { + const result: number[][] = []; + // 투 포인터 사용하기 위해서 배열 정렬 + nums.sort((a, b) => a - b); + + for (let i = 0; i < nums.length - 2; i++) { + // 중복 조합 제거하기 위해서 같은 값인 경우 넘어가도록 처리 + if (i > 0 && nums[i] === nums[i - 1]) continue; + + let leftPointer = i + 1, + rightPointer = nums.length - 1; + + while (leftPointer < rightPointer) { + const sum = nums[i] + nums[leftPointer] + nums[rightPointer]; + + if (sum < 0) leftPointer++; + else if (sum > 0) rightPointer--; + else { + result.push([nums[i], nums[leftPointer], nums[rightPointer]]); + + // 중복 조합 제거하기 위해서 같은 값인 경우 넘어가도록 처리 + while ( + leftPointer < rightPointer && + nums[leftPointer] === nums[leftPointer + 1] + ) + leftPointer++; + while ( + leftPointer < rightPointer && + nums[rightPointer] === nums[rightPointer - 1] + ) + rightPointer--; + leftPointer++; + rightPointer--; + } + } + } + + return result; +} diff --git a/climbing-stairs/mmyeon.ts b/climbing-stairs/mmyeon.ts new file mode 100644 index 000000000..ea8b3d340 --- /dev/null +++ b/climbing-stairs/mmyeon.ts @@ -0,0 +1,30 @@ +/** + * + * 접근 방법 : dp 사용 + * - 3번째 스텝부터 이전 값 2개의 합으로 구할 수 있다. + * - 반복문을 통해서, 저장해놓은 이전값의 합으로 현재값 구하기 + * - 다음값 구하기 위해서, 이전값을 이전이전값으로, 현재값을 이전값으로 업데이트해주기 + * - 현재값 리턴하기 + * + * 시간복잡도 : + * - 0부터 주어진 n번째스텝까지 순회해야 하므로 O(n) + * + * 공간복잡도 : + * - 이전값 저장하기 위해서 변수 2개가 쓰이므로 O(1) + * + */ + +function climbStairs(n: number): number { + if (n <= 2) return n; + + let prevPrevSteps = 1; + let prevSteps = 2; + + for (let i = 3; i <= n; i++) { + const currentSteps = prevPrevSteps + prevSteps; + prevPrevSteps = prevSteps; + prevSteps = currentSteps; + } + + return prevSteps; +} diff --git a/construct-binary-tree-from-preorder-and-inorder-traversal/mmyeon.ts b/construct-binary-tree-from-preorder-and-inorder-traversal/mmyeon.ts new file mode 100644 index 000000000..12873fb25 --- /dev/null +++ b/construct-binary-tree-from-preorder-and-inorder-traversal/mmyeon.ts @@ -0,0 +1,68 @@ +export class TreeNode { + val: number; + left: TreeNode | null; + right: TreeNode | null; + constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { + this.val = val === undefined ? 0 : val; + this.left = left === undefined ? null : left; + this.right = right === undefined ? null : right; + } +} + +/** + * + * 접근 방법 + * - preorder는 root -> left -> right 순서로 진행되니까 첫 번째 요소가 root노드 값인 점을 이용 + * - preorder에서 root 노드 값 파악 + * - inorder(left -> root -> right)에서 head 노드 기준으로 왼쪽 서브 트리, 오른쪽 하위 서브 나누기 + * - inorder의 왼쪽 트리 노드 개수 활용해서 preorder도 왼쪽, 오른쪽 나누기 + * - 재귀 함수를 통해서 위 과정 반복하기 + * - 재귀 함수 기저 조건으로 빈 배열이 들어오는 경우 null처리 + * + * 시간복잡도 : O(n) + * - forEach문으로 map에 값 초기화하니까 O(n) + * - dfs가 각 노드 방문해서 노드 개수 n만큼 호출하니까 O(n) + * + * 공간복잡도 : O(n) + * - indexMap - n이 노드의 개수일 때 map에 노드의 인덱스 모두 저장하니까 O(n) + * - 최악의 경우 한쪽으로 치우친 트리의 경우 재귀 호출 O(n) + * + */ +function buildTree(preorder: number[], inorder: number[]): TreeNode | null { + // index 미리 map에 저장해두기 + const indexMap = new Map(); + inorder.forEach((number, index) => indexMap.set(number, index)); + + // preorder index, inorder range를 전달하기 + const dfs = ( + preorderIndex: number, + inorderStartIndex: number, + inorderEndIndex: number + ): TreeNode | null => { + // 기저 조건 + if ( + !(preorderIndex < preorder.length && inorderStartIndex <= inorderEndIndex) + ) + return null; + + const rootValue = preorder[preorderIndex]; + const inorderRootIndex = indexMap.get(rootValue) as number; + + // 왼쪽 하위 트리 범위 = inorder 배열의 start부터 root인덱스 이전까지 + const left = dfs( + preorderIndex + 1, + inorderStartIndex, + inorderRootIndex - 1 + ); + // 오른쪽 하위 트리 범위 = root인덱스 다음부터 끝까지 + const right = dfs( + preorderIndex + 1 + (inorderRootIndex - inorderStartIndex), + inorderRootIndex + 1, + inorderEndIndex + ); + + return new TreeNode(rootValue, left, right); + }; + + return dfs(0, 0, inorder.length - 1); +} diff --git a/valid-anagram/mmyeon.ts b/valid-anagram/mmyeon.ts new file mode 100644 index 000000000..729f1278e --- /dev/null +++ b/valid-anagram/mmyeon.ts @@ -0,0 +1,36 @@ +/** + * + * 접근 방법 : + * - 두 문자 정렬하면 O(nlogn)이니까 정렬 대신 객체 사용해서 빈도수 체크하는 방법으로 선택 + * - 첫 번쨰 문자열 순회해서 객체에 문자별 빈도수 저장하고, 두 번째 문자열 순회하면서 빈도수 감소시키기 + * - 모든 문자의 빈도수가 0이 되어야 anagram이라는 의미니까, 0인 경우 true 리턴 + * + * 시간복잡도 : + * - 두 객체 for문으로 순회해야 하니까 O(n) + * + * 공간복잡도 : + * - 문자 빈도수를 객체의 크기는 입력 문자열 길이에 비레하니까 O(n) + * + */ + +function isAnagram(s: string, t: string): boolean { + // 두 문자열 길이가 다른 경우는 anagram이 될 수 없으니까 초기 리턴 처리 + if (s.length !== t.length) return false; + + const charCount: Record = {}; + + for (const letter of s) { + charCount[letter] = (charCount[letter] ?? 0) + 1; + } + + for (const letter of t) { + if (!charCount[letter]) return false; + charCount[letter]--; + } + + for (const count in charCount) { + if (charCount[count] !== 0) return false; + } + + return true; +}