Skip to content
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

[mallayon] Week 6 #899

Merged
merged 6 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions container-with-most-water/mmyeon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
*@link https://leetcode.com/problems/container-with-most-water/description/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다 👍 리뷰할 때 문제 보기 너무 좋아요!

*
* 접근 방법 :
* - 가장 많은 물의 양을 구하기 위해서 투 포인터 사용
* - 한 높이가 낮으면 다른 높이가 아무리 높아도 의미가 없어서, 작은 높이를 가진 포인터 이동하며 계산
* - 양 끝에서 포인터 이동할 때마다 최대값 갱신
*
* 시간복잡도 : O(n)
* - 두 포인터가 배열 양 끝에서 1번씩 이동하므로
*
* 공간복잡도 : O(1)
* - 포인터(left,right)와 최대값 변수만 사용
*/
function maxArea(height: number[]): number {
let left = 0,
right = height.length - 1,
maxWater = 0;

while (left < right) {
const width = right - left;
const minHeight = Math.min(height[left], height[right]);
maxWater = Math.max(maxWater, width * minHeight);

if (height[left] < height[right]) {
left++;
} else {
right--;
}
}

return maxWater;
}
123 changes: 123 additions & 0 deletions design-add-and-search-words-data-structure/mmyeon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
*@link https://leetcode.com/problems/design-add-and-search-words-data-structure/
*
* 접근 방법 :
* - set 활용하는 방법
* - 단어 추가할 때는 set에 저장하고, 단어 찾을 때마다 cache목록에서 값 가져오기.
* - 단어 추가하면 이전 캐시는 사용하지 못하니까 클리어 해주기
* - 이전 값 활용한다는 장점이 있지만 단어 추가시 다시 캐시 만들어야하는 비효율 존재
*
* 시간복잡도 : O(n * k)
* - n은 set에 추가된 단어 길이, k는 각 단어 평균 길이
* - this.set 단어 순회 => O(n)
* - replaceAll, filter => O(k)
*
* 공간복잡도 : O(n * k)
* - n은 패턴의 개수, k는 패턴에 매칭되는 평균 단어 수
*/
class WordDictionary {
set: Set<string>;
cache: Map<string, string[]>;
constructor() {
this.set = new Set();
this.cache = new Map();
}

addWord(word: string): void {
this.set.add(word);
// 새로운 단어가 추가 시 캐시 클리어
this.cache.clear();
}

search(word: string): boolean {
// 캐시에 값이 존재하면 즉시 리턴
if (this.cache.has(word)) return this.cache.get(word)!.length > 0;

// 정규표현식 패턴 생성
const pattern = new RegExp(`^${word.replaceAll(".", "[a-z]")}$`);
const matches = [...this.set].filter((item) => pattern.test(item));

// 검색 결과 캐시에 저장
this.cache.set(word, matches);

return matches.length > 0;
}
}

/**
* Your WordDictionary object will be instantiated and called as such:
* var obj = new WordDictionary()
* obj.addWord(word)
* var param_2 = obj.search(word)
*/

// 시간 복잡도 개선하기 위해서 Trie 자료 구조 사용
class TrieNode {
children: Map<string, TrieNode>;
isEndOfWord: boolean;

constructor() {
this.children = new Map();
this.isEndOfWord = false;
}
}

/*
* 접근 방법 :
* - 각 문자 저장하고 검색하기 위해서 Trie 구조 사용
* - 와일드카드(.)가 포함된 단어는 모든 자식 노드 탐색하기 위해서 재귀적으로 확인
*
* 시간복잡도 : O(n * k)
* - n : 단어 개수, m : 단어 평균 길이 => addWord의 복잡도 : O(n * m)
* - c : 자식노드 개수, d : 와일드카드 개수, m : 단어 길이 => search의 복잡도 : O(c^d * m)
* - 와일드 카드 있는 경우 각 노드 별 모든 자식 노드를 탐색해야 한다.
*
* 공간복잡도 : O(n * k)
* - n은 패턴의 개수, k는 패턴에 매칭되는 평균 단어 수
*/
class WordDictionary {
root: TrieNode;
constructor() {
this.root = new TrieNode();
}

addWord(word: string): void {
let currentNode = this.root;

for (const letter of word) {
if (!currentNode.children.has(letter))
currentNode.children.set(letter, new TrieNode());
currentNode = currentNode.children.get(letter)!;
}

currentNode.isEndOfWord = true;
}

search(word: string): boolean {
const dfs = (currentNode: TrieNode, index: number): boolean => {
if (index === word.length) return currentNode.isEndOfWord;

const char = word[index];
// 와일드 카드인 경우 모든 자식 노드 순회한다
if (char === ".") {
// 모든 자식 노드 순회
for (const key of currentNode.children.keys()) {
if (dfs(currentNode.children.get(key)!, index + 1)) return true;
}
return false;
} else {
if (!currentNode.children.has(char)) return false;
return dfs(currentNode.children.get(char)!, index + 1);
}
};

return dfs(this.root, 0);
}
}

/**
* Your WordDictionary object will be instantiated and called as such:
* var obj = new WordDictionary()
* obj.addWord(word)
* var param_2 = obj.search(word)
*/
64 changes: 64 additions & 0 deletions longest-increasing-subsequence/mmyeon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
*
* @link https://leetcode.com/problems/longest-increasing-subsequence/description/
*
* 접근 방법 : dp 사용
* - dp[i]에 i번째 요소 포함한 LIS 길이 저장
* - 현재 값이 이전값보다 큰 경우, 이전값 순회하면서 LIS 업데이트하기
*
* 시간복잡도 : O(n^2)
* - 각 nums 순회하면서 이전값 비교하기 위해 순회하니까 O(n^2)
*
* 공간복잡도 : O(n)
* - nums 길이만큼 dp 배열 생성하니까 O(n)
*/

function lengthOfLIS(nums: number[]): number {
const dp = Array(nums.length).fill(1);

for (let i = 1; i < nums.length; i++) {
for (let j = 0; j < i; j++) {
if (nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
}
}

return Math.max(...dp);
}

/**
*
* 접근 방법 : 이진 탐색 사용
* - num 삽입할 인덱스 찾기 위해서 이진 탐색 사용
* - nums 배열 순회하면서 각 num의 위치 찾기
* - 위치가 배열 내에 존재하면 기존 값을 대체하고, 위치가 배열 길이보다 크면 배열에 추가
*
* 시간복잡도 : O(nlogn)
* - nums 배열의 각 요소에 대해 이진 탐색 O(nlogn)
*
* 공간복잡도 : O(n)
* - arr의 최대 크기가 nums 길이만큼 필요
*/

function lengthOfLIS(nums: number[]): number {
const arr: number[] = [];

nums.forEach((num) => {
let left = 0,
right = arr.length;
// num 삽입할 위치를 이진 탐색으로 찾음
while (left < right) {
const mid = Math.floor((left + right) / 2);

if (arr[mid] < num) {
left = mid + 1;
} else right = mid;
}

// 기존 값 대체
if (left < arr.length) arr[left] = num;
// 새로운 값 추가
else arr.push(num);
});

return arr.length;
}
59 changes: 59 additions & 0 deletions spiral-matrix/mmyeon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @link https://leetcode.com/problems/spiral-matrix/description/
*
* 접근 방법 :
* - right -> bottom -> left -> top 순서로 진행하면서 경계 업데이트 처리
*
* 시간복잡도 : O(n)
* - n은 행렬의 모든 숫자로, 모든 숫자를 한 번씩 방문하므로 O(n)
*
* 공간복잡도 : O(n)
* - 모든 숫자를 저장하기 위해서 배열 사용하므로 O(n)
*/

function spiralOrder(matrix: number[][]): number[] {
const result: number[] = [];
// 경계 설정
let top = 0,
bottom = matrix.length - 1,
left = 0,
right = matrix[0].length - 1;

const moveRight = () => {
for (let i = left; i <= right; i++) {
result.push(matrix[top][i]);
}
top++;
};

const moveDown = () => {
for (let i = top; i <= bottom; i++) {
result.push(matrix[i][right]);
}
right--;
};

const moveLeft = () => {
for (let i = right; i >= left; i--) {
result.push(matrix[bottom][i]);
}
bottom--;
};

const moveUp = () => {
for (let i = bottom; i >= top; i--) {
result.push(matrix[i][left]);
}
left++;
};

while (top <= bottom && left <= right) {
moveRight();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정말 깔끔하네요 👍

moveDown();

if (top <= bottom) moveLeft();
if (left <= right) moveUp();
}

return result;
}
38 changes: 38 additions & 0 deletions valid-parentheses/mmyeon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
*@link https://leetcode.com/problems/valid-parentheses/description/
*
* 접근 방법 :
* - 닫힌 괄호 나왔을 때 열린 괄호의 마지막 값과 짝이 맞는지 찾아야되니까 stack 사용
*
* 시간복잡도 : O(n)
* - 문자열 순회하면서 괄호체크하니까 O(n)
*
* 공간복잡도 : O(k)
* - pairs 객체 고정된 크기로 저장 O(1)
* - k는 열린 괄호 개수, stack에 전체 문자열 중 k만큼만 담기니까 O(k)
*/

function isValid(s: string): boolean {
const pairs: Record<string, string> = {
")": "(",
"}": "{",
"]": "[",
};
const stack: string[] = [];

for (const char of s) {
// 닫는 괄호 나온 경우 처리
if (pairs[char]) {
// 짝이 맞지 않는 경우
if (stack[stack.length - 1] !== pairs[char]) return false;

// 짝이 맞는 경우
stack.pop();
} else {
// 열린 괄호는 바로 stack에 저장
stack.push(char);
}
}

return stack.length === 0;
}
Loading