-
-
Notifications
You must be signed in to change notification settings - Fork 195
[clara-shin] WEEK 06 solutions #1420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8b03d6a
e6eb0d3
e6bd7eb
4067e25
a23b16b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 빠르게 종료할 수 있는 조건을 추가해두면 런타임 측면에서 확실히 좋아질 것 같네요! |
||
|
||
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(']'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
const mapping = {
'(': ')',
'{': '}',
'[': ']'
};
...
for (let char of s) {
if (char in mapping) {
stack.push(mapping[char]);
} else {
...
}
}
} |
||
} else if (stack.length === 0 || stack.pop() !== char) { | ||
// 닫는 괄호를 만났을 때, 스택이 비어있거나 짝이 맞지 않음 | ||
return false; | ||
} | ||
} | ||
|
||
return stack.length === 0; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
문제 풀이 로직, 복잡도 분석, 그리고 코드 라인별 코멘트를 잘 정리하신 걸 보고 많이 배우고 갑니다...! 🤩