Skip to content
Open
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
43 changes: 43 additions & 0 deletions 3sum/Zero-1016.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Two Pointers, Sorting
  • 설명: 코드는 정렬 후 두 포인터(left, right)를 사용해 삼중합의 합이 0이 되도록 탐색합니다. 중복 제거를 위해 같은 값 건너뛰기 로직도 포함되어 있어 투 포인터 패턴이 핵심입니다.

📊 시간/공간 복잡도 분석

ℹ️ 이 파일에는 5가지 풀이가 포함되어 있어 각각 분석합니다.

풀이 1: threeSum — Time: ✅ O(n²) → O(n^2) / Space: ❌ O(log n) → O(1)
유저 분석 실제 분석 결과
Time O(n²) O(n^2)
Space O(log n) O(1)

피드백: 정렬과 투 포인터를 이용해 모든 3합을 찾고, 중복을 양 끝에서 건너뛰는 방식으로 제거합니다.

개선 제안: 현재 구현이 적절해 보입니다.

풀이 2: climbStairs — Time: ❌ O(n²) → O(n) / Space: ❌ O(log n) → O(n)
유저 분석 실제 분석 결과
Time O(n²) O(n)
Space O(log n) O(n)

피드백: 앞선 두 수의 합으로 현재의 경우의 수를 구하는 일반적인 DP 풀이입니다.

개선 제안: 현재 구현이 적절해 보입니다.

풀이 3: productExceptSelf — Time: ❌ O(n²) → O(n) / Space: ❌ O(log n) → O(1)
유저 분석 실제 분석 결과
Time O(n²) O(n)
Space O(log n) O(1)

피드백: 두 방향 누적곱으로 모든 원소의 곱을 구하는 표준 풀이입니다.

개선 제안: 현재 구현이 적절해 보입니다.

풀이 4: isAnagram — Time: ❌ O(n²) → O(n) / Space: ❌ O(log n) → O(1)
유저 분석 실제 분석 결과
Time O(n²) O(n)
Space O(log n) O(1)

피드백: 두 맵을 사용해 빈도수를 비교하되, 배열 기반 카운트가 더 빠르고 간결합니다.

개선 제안: 현재 구현이 적절해 보입니다.

풀이 5: isValidBST — Time: O(n) / Space: O(n)
복잡도
Time O(n)
Space O(n)

피드백: 각 노드에 대해 유효한 범위를 추적하는 표준 방식입니다.

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

제가 작성한 복잡도랑 다르게 표기가 되어있네요 :(

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* 시간 복잡도 O(n²)
* 공간 복잡도 O(log n)
*
* 접근: 오름차순 정렬 + 투 포인터
* - 기준 인덱스 i를 고정하고, left = i + 1, right = 끝에서 탐색
* - 정렬된 상태에서 같은 값을 건너뛰는 방식으로 중복 제거 (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;

// 최적화: 기준 숫자가 양수면 뒤의 숫자들도 모두 양수 → 합이 0 불가능
if (nums[i] > 0) break;

let left = i + 1;
let right = nums.length - 1;

while (left < right) {
const sum = nums[i] + nums[left] + nums[right];

if (sum === 0) {
result.push([nums[i], nums[left], nums[right]]);

while (left < right && nums[left] === nums[left + 1]) left++;
while (left < right && nums[right] === nums[right - 1]) right--;

left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}

return result;
}
19 changes: 19 additions & 0 deletions climbing-stairs/Zero-1016.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Dynamic Programming
  • 설명: 클라이밍 스탯 문제는 각 계단 수를 두 가지 방법으로 오르는 경우의 합으로 누적 최댓값을 구하는 전형적인 DP 문제이며, 점화식으로 순서를 계산합니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* 시간 복잡도 O(n)
* 공간 복잡도 O(n)
*/
function climbStairs(n: number): number {
// NOTE: n이 1일 경우 바로 종료
if (n === 1) return 1;

const dp = new Array(n).fill(0);

dp[0] = 1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

dp[i]가 0부터 시작하다보니, 마치 0개 계단의 경우 1칸 이동을 나타내는 것처럼 느껴집니다.
저도 인덱스를 0부터 자주 사용하는데요. 이 경우, 인덱스를 1부터 사용하면 dp[i] = i칸 계단의 답으로 읽혀 더 직관적이고, base case 설정에 따라 early return도 자연스럽게 제거할 수 있을 것 같다는 의견을 드려봅니다..!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

의견 감사합니다! 사실 저도 dp[1] 부터 사용하는걸 생각하긴 했는데요. dp[0]을 비워두면 실제로 쓰이지 않는 값이 배열에 남는 게 어색해서, 슬롯을 전부 의미 있게 채우는 쪽(dp[i] = i+1칸의 답)으로 작성했어요. 공간 차이는 미미하지만, "배열의 모든 원소가 유효한 답"이라는 일관성을 유지하고 싶었습니다.

dp[1] = 2;

for (let i = 2; i < dp.length; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}

return dp[dp.length - 1];
}
23 changes: 23 additions & 0 deletions product-of-array-except-self/Zero-1016.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Two Pointers, Hash Map / Hash Set
  • 설명: 왼쪽과 오른쪽에서 곱을 누적하는 방식으로 각 원소를 제외한 곱을 구하므로 두 포인터처럼 양 방향으로 누적하는 아이디어가 보이고, 직접적인 해시 자료구조의 사용은 없지만 두 방향 누적 방식이 핵심 패턴으로 작동합니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* 시간복잡도 O(n)
* 공간복잡도 O(n)
*/
function productExceptSelf(nums: number[]): number[] {
const n_length = nums.length;
const result = new Array(n_length).fill(1);

// NOTE: 왼쪽 -> 오른쪽, 오른쪽 -> 왼쪽 순회하면서 값을 누적함.
let left = 1;
for (let i = 0; i < n_length; i++) {
result[i] *= left;
left *= nums[i];
}

let right = 1;
for (let i = n_length - 1; i >= 0; i--) {
result[i] *= right;
right *= nums[i];
}

return result;
}
26 changes: 26 additions & 0 deletions valid-anagram/Zero-1016.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Hash Map / Hash Set, Greedy
  • 설명: 두 문자열의 문자 빈도를 해시 맵으로 카운트하고, 이를 비교하여 anagram 여부를 판단한다. 해시 맵을 활용한 빈도 비교 패턴이 핵심이다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* 242. Valid Anagram
* Given two strings s and t, return true if t is an anagram of s, and false otherwise.
*
* 시간복잡도: O(n)
* 공간복잡도: O(n)
*/
function isAnagram(s: string, t: string): boolean {
if (s.length !== t.length) return false;

const sMap = new Map<string, number>();
const tMap = new Map<string, number>();

// 문자열 s와 t를 순회하면서 각 문자의 빈도수를 계산
for (let i = 0; i < s.length; i++) {
sMap.set(s[i], (sMap.get(s[i]) || 0) + 1);
tMap.set(t[i], (tMap.get(t[i]) || 0) + 1);
}

// 문자열 s와 t의 빈도수를 비교
for (const [key, value] of sMap) {
if (value !== tMap.get(key)) return false;
}

return true;
}
40 changes: 40 additions & 0 deletions validate-binary-search-tree/Zero-1016.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Depth-First Search, Binary Search, Monotonic Stack, Hash Map / Hash Set
  • 설명: 반복적 DFS로 트리 노드를 순회하며 각 노드의 값이 부모가 정한 범위 안에 있는지 검사한다. 이와 동시에 BST의 성질을 만족하는지 확인하며, 범위 갱신은 자식 노드 방향에 따라 min/max를 업데이트한다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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;
}
}
type Frame = {
node: TreeNode | null;
min: number;
max: number;
};

/**
* 반복적 DFS로 각 노드가 부모 노드가 정한 값 범위 (min, max) 안에 있는지 검사한다.
* - 왼쪽 자식으로 내려가면 상한(max)이 현재 노드 값으로 좁아지고,
* - 오른쪽 자식으로 내려가면 하한(min)이 현재 노드 값으로 좁아진다.
*
* 시간 복잡도: O(n) — 모든 노드를 한 번씩 방문
* 공간 복잡도: O(n) — 최악의 경우 스택 크기 (평균적으로는 O(h))
*/
function isValidBST(root: TreeNode | null): boolean {
const stack: Frame[] = [{ node: root, min: -Infinity, max: Infinity }];

while (stack.length > 0) {
const { node, min, max } = stack.pop()!;
if (!node) continue;

// NOTE 부모 노드들이 정한 범위를 벗어나면 유효한 BST가 아니다.
if (node.val <= min || node.val >= max) return false;

stack.push({ node: node.left, min, max: node.val });
stack.push({ node: node.right, min: node.val, max });
}

return true;
}
Loading