From 119a30e50580ec188e3d6b8abe678da03e51c386 Mon Sep 17 00:00:00 2001 From: yoouyeon Date: Mon, 7 Apr 2025 08:55:11 +0900 Subject: [PATCH 1/5] add: Valid Anagram solution --- valid-anagram/yoouyeon.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 valid-anagram/yoouyeon.ts diff --git a/valid-anagram/yoouyeon.ts b/valid-anagram/yoouyeon.ts new file mode 100644 index 000000000..485b6f7ea --- /dev/null +++ b/valid-anagram/yoouyeon.ts @@ -0,0 +1,28 @@ +/** + * [Idea] + * s에서 각 문자가 몇번 등장하는지 세어준다. (charCount) + * t에서 문자의 개수를 세어주면서 charCount의 값을 감소시킨다. + * 문자의 개수가 부족하거나 남는다면 애너그램이 아니다. + * + * [Time Complexity] + * O(n + n + k) => O(n) (n: s와 t의 길이, k: 두 문자열에 등장하는 고유 문자 수) + * + * [Space Complexity] + * O(n) + * 문자 등장 횟수를 세어주는 charCount에 필요한 공간 + */ +function isAnagram(s: string, t: string): boolean { + const charCount: Record = {}; + for (const char of s) { + charCount[char] = (charCount[char] ?? 0) + 1; + } + + for (const char of t) { + if (charCount[char] === undefined || charCount[char] === 0) { + return false; + } + charCount[char]--; + } + + return Object.values(charCount).every((count) => count === 0); +} From eebeb276d5174cefd5717963132ff56aed5acc1b Mon Sep 17 00:00:00 2001 From: yoouyeon Date: Mon, 7 Apr 2025 08:58:29 +0900 Subject: [PATCH 2/5] add: Climbing Stairs solution --- climbing-stairs/yoouyeon.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 climbing-stairs/yoouyeon.ts diff --git a/climbing-stairs/yoouyeon.ts b/climbing-stairs/yoouyeon.ts new file mode 100644 index 000000000..2706cdac1 --- /dev/null +++ b/climbing-stairs/yoouyeon.ts @@ -0,0 +1,24 @@ +/** + * [Idea] - Dynamic programming + * 현재 계단에 도달하기 위해서는 1칸 전 계단에서 1칸 올라오거나 2칸 전 계단에서 2칸 올라와야 한다. + * memo[i] = memo[i - 1] + memo[i - 2] 로 계산할 수 있다. + * + * [Time Complexity] + * O(n) (n: 계단의 개수) + * 계단의 개수만큼 반복문을 돈다. + * + * [Space Complexity] + * O(n) (n: 계단의 개수) + * memo 배열을 n + 1 크기로 생성한다. + */ +function climbStairs(n: number): number { + const memo = new Array(n + 1).fill(0); + memo[1] = 1; + memo[2] = 2; + + for (let currStep = 3; currStep <= n; currStep++) { + memo[currStep] = memo[currStep - 1] + memo[currStep - 2]; + } + + return memo[n]; +} From 6f11cdcade25bfc102100e3caeb89f7f1c205549 Mon Sep 17 00:00:00 2001 From: yoouyeon Date: Fri, 11 Apr 2025 09:10:37 +0900 Subject: [PATCH 3/5] add: Product of Array Except Self solution --- product-of-array-except-self/yoouyeon.ts | 44 ++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 product-of-array-except-self/yoouyeon.ts diff --git a/product-of-array-except-self/yoouyeon.ts b/product-of-array-except-self/yoouyeon.ts new file mode 100644 index 000000000..dcf9efe39 --- /dev/null +++ b/product-of-array-except-self/yoouyeon.ts @@ -0,0 +1,44 @@ +// [238] Product of Array Except Self + +/** + * [Idea] + * nums[i] 를 기준으로 왼쪽 부분 (prefix)은 계속 구간을 늘려주고, 오른쪽 부분(suffix)은 계속 구간을 줄여주는 방식 + * i = 0: + * [ ] * [2, 3, 4, 5, 6] => prefix: 0, suffix: 2 * 3 * 4 * 5 * 6 + * i = 1: + * [1] * [3, 4, 5, 6] => prefix: 1, suffix: 3 * 4 * 5 * 6 + * i = 2: + * [1, 2] * [4, 5, 6] => prefix: 1 * 2, suffix: 4 * 5 * 6 + * i = 3: + * [1, 2, 3] * [5, 6] => prefix: 1 * 2 * 3, suffix: 5 * 6 + * + * [Time Complexity] + * O(n) (n: nums의 길이) + * 1. prefix를 구하는 과정에서 O(n) + * 2. suffix를 구하는 과정에서 O(n) + * + * [Space Complexity] + * O(1) + * answer 배열을 제외한 추가적인 공간을 사용하지 않음 + * + */ +function productExceptSelf(nums: number[]): number[] { + const n = nums.length; + const answer = new Array(nums.length); + + // answer 배열에 prefix값 설정해주기 + let prefix = 1; + for (let idx = 0; idx < n; idx++) { + answer[idx] = prefix; + prefix *= nums[idx]; + } + + // 각 prefix에 suffix 값 누적해서 곱해주기 + let suffix = 1; + for (let idx = n - 1; idx >= 0; idx--) { + answer[idx] *= suffix; + suffix *= nums[idx]; + } + + return answer; +} From 208641132d17adc30cb5c1c9d78e33ca1ee773b7 Mon Sep 17 00:00:00 2001 From: yoouyeon Date: Fri, 11 Apr 2025 09:21:55 +0900 Subject: [PATCH 4/5] add: 3Sum solution --- 3sum/yoouyeon.ts | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 3sum/yoouyeon.ts diff --git a/3sum/yoouyeon.ts b/3sum/yoouyeon.ts new file mode 100644 index 000000000..fbd7d0b42 --- /dev/null +++ b/3sum/yoouyeon.ts @@ -0,0 +1,62 @@ +// [15] 3Sum + +/** + * 앞서 시도한 방법들 + * - DFS로 모든 조합을 구하는 방법 : Time Limit Exceeded... + * - Two Sum에서 풀었던 방법대로 Set을 이용해서 조합을 만드는 방법 : Time Limit Exceeded... + */ + +/** + * [Idea] + * Two Pointers + * Set을 이용하지 않고 정렬을 통해서 중복된 조합을 제거하는 방법 + * 투포인터 방식으로 x를 고정하고 y, z를 찾는다. + * 중복된 원소는 건너뛰는 방식으로 중복된 조합을 제거한다. + * + * [Time Complexity] + * O(n^2) (n: nums의 원소 개수) + * 시작 원소 (idx)를 고정시켜 nums를 순회하는 데 O(n) 소요되고 + * 내부에서 투포인터로 y, z를 찾는 데 O(n) 소요되므로 O(n^2) + * + * [Space Complexity] + * O(1) + * left, right 변수만 사용하므로 O(1) + */ +function threeSum(nums: number[]): number[][] { + const result: number[][] = []; + nums.sort((a, b) => a - b); + + for (let idx = 0; idx < nums.length - 2; idx++) { + // 같은 숫자로 시작하는 조합을 제거 (시도하지 않는다) + if (idx > 0 && nums[idx] === nums[idx - 1]) continue; + + let left = idx + 1; + let right = nums.length - 1; + + while (left < right) { + const sum = nums[idx] + nums[left] + nums[right]; + + if (sum === 0) { + result.push([nums[idx], nums[left], nums[right]]); + // 중복된 조합을 제거하기 위해서 같은 원소인 경우를 건너뛴다. + while (left < right && nums[left] === nums[left + 1]) { + left++; + } + while (left < right && nums[right] === nums[right - 1]) { + right--; + } + // 다음 조합을 시도하기 + left++; + right--; + continue; + } + if (sum < 0) { + left++; + continue; + } + right--; + } + } + + return result; +} From 2da87c9921a525f4b010213c65d9a79002c98442 Mon Sep 17 00:00:00 2001 From: yoouyeon Date: Sat, 12 Apr 2025 21:10:45 +0900 Subject: [PATCH 5/5] add: Validate Binary Search Tree solution --- validate-binary-search-tree/yoouyeon.ts | 56 +++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 validate-binary-search-tree/yoouyeon.ts diff --git a/validate-binary-search-tree/yoouyeon.ts b/validate-binary-search-tree/yoouyeon.ts new file mode 100644 index 000000000..38b1811fb --- /dev/null +++ b/validate-binary-search-tree/yoouyeon.ts @@ -0,0 +1,56 @@ +// [98] Validate Binary Search Tree + +/** + * [Idea] + * 부모 트리가 BST가 되려면 왼쪽 자식 트리와 오른쪽 자식 트리가 모두 BST여야 한다. + * 이 때 왼쪽 자식 트리의 범위는 "하한"은 계속 유지되고 (음의 무한대), "상한"은 부모 노드의 값으로 업데이트되고 + * 오른쪽 자식 트리의 범위는 "상한"은 계속 유지되고 (양의 무한대), "하한"은 부모 노드의 값으로 업데이트된다. + * (다 푼 뒤에 찾아보니 트리 순회 방식 중 전위순회 방식으로 순회하며 확인하는 것이었다.) + * + * [Time Complexity] + * 모든 노드를 한번씩 확인하므로 O(n) + * + * [Space Complexity] + * 재귀적으로 돌아가는 코드이기 때문에 공간 복잡도는 **재귀 콜 스택**에 의해 결정된다! + * 재귀 호출은 양쪽 트리로 쪼개져서 호출되기 때문에 (왼쪽 다 하고 오른쪽 검사) 콜 스택의 최대 깊이는 트리의 최대 깊이가 된다. + * 최악의 경우 (왼쪽이나 오른쪽 노드만 있는 연결 리스트 형태의 트리) 깊이가 n이 되므로 + * 콜 스택의 깊이가 n이 되어 O(n) + */ + +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; + } +} + +function isValidBST(root: TreeNode | null): boolean { + // 노드가 하나만 있는 경우를 먼저 처리해줬다. + if (root === null || (root.left === null && root.right === null)) { + return true; + } + + function checkSubTree( + currNode: TreeNode | null, + min: number, + max: number + ): boolean { + if (currNode === null) { + return true; + } + if (currNode.val <= min || currNode.val >= max) { + return false; + } + + return ( + checkSubTree(currNode.left, min, currNode.val) && + checkSubTree(currNode.right, currNode.val, max) + ); + } + + return checkSubTree(root, -Infinity, Infinity); +}