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 2 #717

Merged
merged 6 commits into from
Dec 21, 2024
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
60 changes: 60 additions & 0 deletions 3sum/mmyeon.ts
Original file line number Diff line number Diff line change
@@ -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;
}
30 changes: 30 additions & 0 deletions climbing-stairs/mmyeon.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Comment on lines +17 to +30
Copy link
Contributor

Choose a reason for hiding this comment

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

TypeScript는 잘 몰라서 제 파이썬 코드와 비교해봤는데, @mmyeon 님 코드를 통해서 중간 결과를 dp 배열에 모두 저장하는 대신 두 개의 변수만 사용하면 공간복잡도를 더 최적화할 수 있다는 걸 배웠어요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@KwonNayeon
안녕하세요! 귀중한 시간 내어서 코드 리뷰 해주셔서 정말 감사합니다 👍
저도 다른 분들 풀이 참고해서 작성해보았는데 도움이 되셨다니 뿌듯합니다.
다시 한번 감사드립니다 :)

Original file line number Diff line number Diff line change
@@ -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<number, number>();
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);
}
36 changes: 36 additions & 0 deletions valid-anagram/mmyeon.ts
Original file line number Diff line number Diff line change
@@ -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<string, number> = {};

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;
}
Loading