From 8b03d6a87a38334aca4ed46b8ff8dca89ca09f63 Mon Sep 17 00:00:00 2001 From: Hyejin Date: Sun, 4 May 2025 22:35:58 +0900 Subject: [PATCH 1/5] Valid Parentheses Solution --- valid-parentheses/clara-shin.js | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 valid-parentheses/clara-shin.js diff --git a/valid-parentheses/clara-shin.js b/valid-parentheses/clara-shin.js new file mode 100644 index 000000000..3f31b3693 --- /dev/null +++ b/valid-parentheses/clara-shin.js @@ -0,0 +1,37 @@ +/** + * 괄호 문자열 유효성 검사 + * + * 스택 자료구조 활용 + * 1. 괄호 쌍을 매핑하는 객체를 생성하고 조건을 확인 + * 2. 열린 괄호를 만나면 해당하는 닫힌 괄호를 스택에 직접 push + * 3. 닫는 괄호를 만났을 때, 스택이 비어있거나 짝이 맞지 않으면 false + * 4. 문자열을 모두 처리한 후, 스택이 비어있어야(문자열 길이가 0이어야) 모든 괄호가 올바르게 짝지어진 것(true) + */ + +/** + * @param {string} s + * @return {boolean} + */ +var isValid = function (s) { + // 빈 문자열이나 홀수 길이는 유효하지 않음 + if (s.length === 0 || s.length % 2 !== 0) return false; + + const stack = []; + + for (let i = 0; i < s.length; i++) { + const char = s[i]; + + if (char === '(') { + stack.push(')'); + } else if (char === '{') { + stack.push('}'); + } else if (char === '[') { + stack.push(']'); + } else if (stack.length === 0 || stack.pop() !== char) { + // 닫는 괄호를 만났을 때, 스택이 비어있거나 짝이 맞지 않음 + return false; + } + } + + return stack.length === 0; +}; From e6eb0d333b4c4bee3e81b11d3c4f0ffaf6c1e414 Mon Sep 17 00:00:00 2001 From: Hyejin Date: Wed, 7 May 2025 06:00:55 +0900 Subject: [PATCH 2/5] Container With Most Water Solution --- container-with-most-water/clara-shin.js | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 container-with-most-water/clara-shin.js diff --git a/container-with-most-water/clara-shin.js b/container-with-most-water/clara-shin.js new file mode 100644 index 000000000..1dbf03453 --- /dev/null +++ b/container-with-most-water/clara-shin.js @@ -0,0 +1,42 @@ +/** + * 두 선을 선택해 물을 담을 수 있는 최대 면적을 구하는 문제 + * + * 가로 길이: 두 선 사이의 거리(인덱스 차이) + * 세로 길이: 두 선 중 더 짧은 높이(물은 낮은 쪽으로 넘치기 때문) + * 면적 = 가로 길이 × 세로 길이 + * + * 양쪽 끝에서 시작하는 두 포인터를 사용 + * 투 포인터 방식이 효율적인 이유: 항상 더 작은 높이를 가진 쪽을 이동시키면 최대 면적을 놓치지 않기 때문 + * 더 큰 높이 쪽을 이동시키면 가로 길이는 줄어들고, 세로 길이는 같거나 더 작아져서 면적이 줄어들 수밖에 없음 + * + * 시간 복잡도: O(n) - 배열을 한 번만 순회 + * 공간 복잡도: O(1) - 추가 공간 사용 없음 + */ +/** + * @param {number[]} height + * @return {number} + */ +var maxArea = function (height) { + let left = 0; + let right = height.length - 1; + let maxWater = 0; + + while (left < right) { + // 현재 두 선으로 만들 수 있는 물의 양 계산 + const width = right - left; + const minHeight = Math.min(height[left], height[right]); + const water = width * minHeight; + + // 최대값 업데이트 + maxWater = Math.max(maxWater, water); + + // 더 작은 높이를 가진 쪽의 포인터를 이동시킴 + if (height[left] < height[right]) { + left++; + } else { + right--; + } + } + + return maxWater; +}; From e6bd7eb52ed9dde1907783509a8046a9778e67a2 Mon Sep 17 00:00:00 2001 From: Hyejin Date: Thu, 8 May 2025 19:02:29 +0900 Subject: [PATCH 3/5] Design Add and Search Words Data Structure Solution --- .../clara-shin.js | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 design-add-and-search-words-data-structure/clara-shin.js diff --git a/design-add-and-search-words-data-structure/clara-shin.js b/design-add-and-search-words-data-structure/clara-shin.js new file mode 100644 index 000000000..ed5ed081a --- /dev/null +++ b/design-add-and-search-words-data-structure/clara-shin.js @@ -0,0 +1,130 @@ +/** + * 단어를 저장하고 검색할 수 있는 자료구조 만들기 + * + * 트라이(Trie) 자료구조: 문자열 집합을 표현하는 트리 기반 자료구조, (문자열 검색에 효율적) + - 각 노드는 문자 하나를 나타냄 + - 루트에서 특정 노드까지의 경로는 하나의 문자열을 나타냄 + - 각 노드는 자식 노드들을 가리키는 링크(보통 해시맵)를 가짐 + - 단어의 끝을 표시하는 플래그가 필요함 + * + * 대부분의 검색은 와일드카드가 없는 경우일 테니까, 이것만 처리하는 별도 메서드로 뽑음(와일드카드 분기처리) + * + * 시간복잡도: O(m) (m: 단어의 길이) + * 공간복잡도: O(n) (n: 단어의 개수) + */ + +var WordDictionary = function () { + // 트라이 노드 클래스 정의 + this.TrieNode = function () { + this.children = {}; // 자식 노드들을 저장하는 해시맵 + this.isEnd = false; // 단어의 끝을 표시하는 플래그 + }; + + this.root = new this.TrieNode(); // 루트 노드 생성 +}; + +/** + * 단어를 트라이에 추가 + * @param {string} word + * @return {void} + */ +WordDictionary.prototype.addWord = function (word) { + let node = this.root; + + // 단어의 각 문자를 순회하며 트라이에 추가 + for (let i = 0; i < word.length; i++) { + const char = word[i]; + + // 현재 문자에 해당하는 자식 노드가 없으면 생성 + if (!node.children[char]) { + node.children[char] = new this.TrieNode(); + } + + // 다음 레벨로 이동 + node = node.children[char]; + } + + // 단어의 끝 표시 + node.isEnd = true; +}; + +/** + * 트라이에서 단어 검색 (와일드카드 '.' 지원) + * @param {string} word + * @return {boolean} + */ +WordDictionary.prototype.search = function (word) { + // 와일드카드가 없는 경우 + if (!word.includes('.')) { + return this.searchExact(word); + } + + return this.searchWithWildcard(word, 0, this.root); +}; + +/** + * 와일드카드 없이 정확한 단어 검색 + * @param {string} word + * @return {boolean} + */ +WordDictionary.prototype.searchExact = function (word) { + let node = this.root; + + for (let i = 0; i < word.length; i++) { + const char = word[i]; + + // 해당 문자의 자식 노드가 없으면 false + if (!node.children[char]) { + return false; + } + + node = node.children[char]; + } + + // 단어의 끝에 도달했을 때 isEnd 플래그 확인 + return node.isEnd; +}; + +/** + * 와일드카드를 포함한 단어 검색 (재귀적) + * @param {string} word - 검색할 단어 + * @param {number} index - 현재 검색중인 문자 인덱스 + * @param {object} node - 현재 검색중인 노드 + * @return {boolean} + */ +WordDictionary.prototype.searchWithWildcard = function (word, index, node) { + // 단어의 모든 문자를 검사했으면 + if (index === word.length) { + return node.isEnd; + } + + const char = word[index]; + + // 와일드카드('.')인 경우 + if (char === '.') { + // 현재 노드의 모든 자식에 대해 재귀적으로 검색 + for (const key in node.children) { + if (this.searchWithWildcard(word, index + 1, node.children[key])) { + return true; + } + } + return false; + } + // 일반 문자인 경우 + else { + // 해당 문자에 대한 자식 노드가 없으면 false + if (!node.children[char]) { + return false; + } + + // 다음 문자 검색 + return this.searchWithWildcard(word, index + 1, node.children[char]); + } +}; + +/** + * Your WordDictionary object will be instantiated and called as such: + * var obj = new WordDictionary() + * obj.addWord(word) + * var param_2 = obj.search(word) + */ From 4067e250908f4bfe75b35551d0f147236c3197bb Mon Sep 17 00:00:00 2001 From: Hyejin Date: Thu, 8 May 2025 19:16:04 +0900 Subject: [PATCH 4/5] Longest Increasing Subsequence Solution --- longest-increasing-subsequence/clara-shin.js | 71 ++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 longest-increasing-subsequence/clara-shin.js diff --git a/longest-increasing-subsequence/clara-shin.js b/longest-increasing-subsequence/clara-shin.js new file mode 100644 index 000000000..66b7a7863 --- /dev/null +++ b/longest-increasing-subsequence/clara-shin.js @@ -0,0 +1,71 @@ +/** + * 최장 증가 부분 수열(Longest Increasing Subsequence, LIS)'을 구하기 + * 부분 수열(Subsequence): 원래 수열에서 몇 개의 원소를 골라서 순서를 바꾸지 않고 나열한 것 + * 증가 부분 수열(Increasing Subsequence): 부분 수열의 원소들이 오름차순으로 정렬된 것 + * + * 접근방법: 동적계획법(DP) 또는 이진탐색(Binary Search) + * 1. DP를 이용한 방법: 시간복잡도 O(n^2) + * 2. 이진탐색을 이용한 방법: 시간복잡도 O(n log n) ✅ follow-up 고려 + */ + +/** 동적계획법(DP)으로 접근 + * @param {number[]} nums + * @return {number} + */ +var lengthOfLIS = function (nums) { + if (nums.length === 0) return 0; + + // dp[i]는 인덱스 i까지의 가장 긴 증가 부분 수열의 길이 + const dp = Array(nums.length).fill(1); + + // 모든 위치에 대해 검사 + for (let i = 1; i < nums.length; i++) { + // 현재 위치보다 이전의 모든 위치를 검사 + for (let j = 0; j < i; j++) { + // 현재 값이 이전 값보다 크면, 이전 위치의 LIS에 현재 값을 추가할 수 있음 + if (nums[i] > nums[j]) { + // 기존 값과 (이전 위치의 LIS 길이 + 1) 중 더 큰 값을 선택 + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + } + + // dp 배열에서 가장 큰 값이 LIS의 길이 + return Math.max(...dp); +}; + +/** 이진탐색(Binary Search)으로 접근 + * @param {number[]} nums + * @return {number} + */ +var lengthOfLIS = function (nums) { + if (nums.length === 0) return 0; + + // tails[i]는 길이가 i+1인 증가 부분 수열의 마지막 원소 중 가장 작은 값 + const tails = []; + + for (let num of nums) { + // 이진 탐색으로 num이 들어갈 위치 찾기 + let left = 0; + let right = tails.length; + + while (left < right) { + const mid = Math.floor((left + right) / 2); + if (tails[mid] < num) { + left = mid + 1; + } else { + right = mid; + } + } + + // 찾은 위치에 num 삽입 또는 대체 + if (left === tails.length) { + tails.push(num); // 새로운 최장 길이 발견 + } else { + tails[left] = num; // 기존 값 갱신 + } + } + + // tails 배열의 길이가 LIS의 길이 + return tails.length; +}; From a23b16bcbf207c4bd0edcdf0ea275d72184c1bb8 Mon Sep 17 00:00:00 2001 From: Hyejin Date: Fri, 9 May 2025 02:48:38 +0900 Subject: [PATCH 5/5] Spiral Matrix Solution --- spiral-matrix/clara-shin.js | 67 +++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 spiral-matrix/clara-shin.js diff --git a/spiral-matrix/clara-shin.js b/spiral-matrix/clara-shin.js new file mode 100644 index 000000000..f35716042 --- /dev/null +++ b/spiral-matrix/clara-shin.js @@ -0,0 +1,67 @@ +/** + * 오른쪽 -> 아래쪽 -> 왼쪽 -> 위쪽 + * --- 행렬의 경계 --- + * top: 위쪽 경계 + * bottom: 아래쪽 경계 + * left: 왼쪽 경계 + * right: 오른쪽 경계 + * ---------------- + * 각 방향으로 한 바퀴를 돌 때마다 경계를 하나씩 줄여가며 모든 요소를 방문 + */ + +/** + * @param {number[][]} matrix + * @return {number[]} + */ +var spiralOrder = function (matrix) { + if (!matrix.length || !matrix[0].length) return []; // 빈 행렬 체크 + + const result = []; // 결과를 저장할 배열 초기화 + + let top = 0; + let bottom = matrix.length - 1; + let left = 0; + let right = matrix[0].length - 1; + + // 아직 처리할 요소가 남아있는 동안 계속 순회 + // top > bottom 또는 left > right가 되면 모든 요소를 방문한 것 + while (top <= bottom && left <= right) { + // 1. 위쪽 행: 왼쪽 → 오른쪽 이동 + for (let i = left; i <= right; i++) { + result.push(matrix[top][i]); + } + // 위쪽 행을 처리했으므로 top 인덱스를 1 증가 + top++; + + // 2. 오른쪽 열: 위 → 아래 이동 + for (let i = top; i <= bottom; i++) { + result.push(matrix[i][right]); + } + // 오른쪽 열을 처리했으므로 right 인덱스를 1 감소 + right--; + + // 3. 아래쪽 행: 오른쪽 → 왼쪽 이동 + // 이미 top이 bottom을 초과한 경우, 아래쪽 행이 존재하지 않으므로 처리하지 않음 + if (top <= bottom) { + // 현재 bottom 행에서 right부터 left까지의 모든 요소를 역순으로 순회 + for (let i = right; i >= left; i--) { + result.push(matrix[bottom][i]); + } + // 아래쪽 행을 처리했으므로 bottom 인덱스를 1 감소 + bottom--; + } + + // 4. 왼쪽 열: 아래 → 위 이동 + // 이미 left가 right를 초과한 경우, 왼쪽 열이 존재하지 않으므로 처리하지 않음 + if (left <= right) { + // 현재 left 열에서 bottom부터 top까지의 모든 요소를 역순으로 순회 + for (let i = bottom; i >= top; i--) { + result.push(matrix[i][left]); + } + // 왼쪽 열을 처리했으므로 left 인덱스를 1 증가 + left++; + } + } + + return result; +};