Skip to content

[clara-shin] WEEK09 Solutions #1526

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

Merged
merged 6 commits into from
May 31, 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
64 changes: 64 additions & 0 deletions linked-list-cycle/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 주어진 연결 리스트에서 사이클이 있는지 판단하는 문제
// 사이클: 연결 리스트의 어떤 노드가, 이전에 방문했던 노드를 다시 가리키는 경우

// 문제접근:
// 1) hashset : 직관적
// 2) two pointers : 플로이드의 사이클 탐지 알고리즘(토끼와 거북이 알고리즘)

/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/

/** two pointers로 접근
* 시간복잡도: O(n)
* 공간복잡도: O(1) - ✅ follow-up 고려
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function (head) {
if (!head || !head.next) return false;

let slow = head; // 거북이: 한 칸씩
let fast = head; // 토끼: 두 칸씩

while (fast && fast.next) {
slow = slow.next; // 한 칸 이동
fast = fast.next.next; // 두 칸 이동

// 두 포인터가 만나면 사이클 존재한다
if (slow === fast) {
return true;
}
}

return false;
};

/**
* hashset으로 접근
* 시간복잡도: O(n)
* 공간복잡도: O(n)
*/
var hasCycle2 = function (head) {
if (!head) return false;

const visited = new Set();
let current = head;

while (current) {
// 이미 방문한 노드라면 사이클 존재한다
if (visited.has(current)) {
return true;
}

// 현재 노드를 방문 기록에 추가
visited.add(current);
current = current.next;
}

return false;
};
41 changes: 41 additions & 0 deletions maximum-product-subarray/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* 정수 배열에서 연속된 부분배열(subarray)의 곱이 최대가 되는 값을 찾는 함수
*
* 음수끼리 곱하면 양수가 됨
* 0이 나오면 곱은 0이 됨 => 부분배열을 새로 시작해야 함
* 최대값과 최소값을 동시에 추적해야 함: 음수의 경우 최대값이 최소값이 될 수 있음
*
* 접근 방법: 동적 프로그래밍
* 시간복잡도: O(n), 공간복잡도: O(1)
*/

/**
* @param {number[]} nums
* @return {number}
*/
var maxProduct = function (nums) {
if (nums.length === 0) return 0;

// 현재 위치에서 끝나는 부분배열의 최대곱과 최소곱
let maxHere = nums[0];
let minHere = nums[0];
let result = nums[0];

for (let i = 1; i < nums.length; i++) {
const num = nums[i];

// 현재 숫자가 음수라면 max와 min이 바뀔 수 있음
if (num < 0) {
[maxHere, minHere] = [minHere, maxHere];
}

// 현재 숫자부터 새로 시작하거나, 기존 곱에 현재 숫자를 곱함
maxHere = Math.max(num, maxHere * num);
minHere = Math.min(num, minHere * num);

// 전체 최대값 업데이트
result = Math.max(result, maxHere);
}

return result;
};
64 changes: 64 additions & 0 deletions minimum-window-substring/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* 문자열 s에서 문자열 t의 모든 문자(중복 포함)를 포함하는 가장 짧은 부분 문자열을 찾는 문제
* 시간복잡도: O(m + n), 공간복잡도: O(m + n)
*/

/**
* @param {string} s
* @param {string} t
* @return {string}
*/
var minWindow = function (s, t) {
if (s.length < t.length) return '';

// t의 각 문자별 필요한 개수를 Map으로 저장
const need = new Map();
for (let char of t) {
need.set(char, (need.get(char) || 0) + 1);
}

let left = 0;
let right = 0;
let valid = 0; // 조건을 만족하는 문자 종류 수
let minLen = Infinity;
let start = 0;

// 현재 윈도우의 문자별 개수
const window = new Map();

while (right < s.length) {
// 윈도우에 문자 추가
const rightChar = s[right];
right++;

// 필요한 문자인 경우에만 처리
if (need.has(rightChar)) {
window.set(rightChar, (window.get(rightChar) || 0) + 1);
if (window.get(rightChar) === need.get(rightChar)) {
valid++;
}
}

// 윈도우 축소 시도
while (valid === need.size) {
// 더 작은 윈도우 발견시 업데이트
if (right - left < minLen) {
start = left;
minLen = right - left;
}

// left를 이동시키며 윈도우에서 문자 제거
const leftChar = s[left];
left++;

if (need.has(leftChar)) {
if (window.get(leftChar) === need.get(leftChar)) {
valid--;
}
window.set(leftChar, window.get(leftChar) - 1);
}
}
}

return minLen === Infinity ? '' : s.substring(start, start + minLen);
};
82 changes: 82 additions & 0 deletions pacific-atlantic-water-flow/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* 그래프 역방향 탐색 : DFS를 사용하여 태평양과 대서양에 도달할 수 있는 모든 지점을 찾는 문제
* 시간 복잡도: O(m × n) - 각 셀을 최대 2번 방문
* 공간 복잡도: O(m × n) - visited 배열과 스택 공간
*/
/**
* @param {number[][]} heights
* @return {number[][]}
*/
var pacificAtlantic = function (heights) {
if (!heights || heights.length === 0) return [];

const m = heights.length;
const n = heights[0].length;

// 비트마스킹 사용한 방문 상태 관리
// 0: 미방문, 1: 태평양만, 2: 대서양만, 3: 둘 다
const visited = Array(m)
.fill()
.map(() => Array(n).fill(0));
const directions = [
[-1, 0],
[1, 0],
[0, -1],
[0, 1],
];
const result = [];

// DFS - 스택 사용
function dfs(startPoints, oceanMask) {
const stack = [...startPoints];

while (stack.length > 0) {
const [row, col] = stack.pop();

// 이미 해당 바다로 표시되어 있으면 건너뛰기
if (visited[row][col] & oceanMask) continue;

// 현재 바다 표시
visited[row][col] |= oceanMask;

// 양쪽 바다 모두 도달 가능하면 결과에 추가
if (visited[row][col] === 3) {
result.push([row, col]);
}

// 4방향 탐색
for (const [dr, dc] of directions) {
const newRow = row + dr;
const newCol = col + dc;

// 경계 확인 및 높이 조건 확인
if (
newRow >= 0 &&
newRow < m &&
newCol >= 0 &&
newCol < n &&
heights[newRow][newCol] >= heights[row][col] &&
!(visited[newRow][newCol] & oceanMask)
) {
stack.push([newRow, newCol]);
}
}
}
}

// 태평양 시작점들
const pacificStarts = [];
for (let i = 0; i < m; i++) pacificStarts.push([i, 0]); // 첫 번째 열
for (let j = 1; j < n; j++) pacificStarts.push([0, j]); // 첫 번째 행 (중복 제거)

// 대서양 시작점들
const atlanticStarts = [];
for (let i = 0; i < m; i++) atlanticStarts.push([i, n - 1]); // 마지막 열
for (let j = 0; j < n - 1; j++) atlanticStarts.push([m - 1, j]); // 마지막 행 (중복 제거)

// 각 바다에서 DFS 실행
dfs(pacificStarts, 1); // 태평양: 비트 1
dfs(atlanticStarts, 2); // 대서양: 비트 2

return result;
};
31 changes: 31 additions & 0 deletions sum-of-two-integers/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 두 정수 a와 b가 주어졌을 때, + 와 - 연산자를 사용하지 않고 두 수의 합 구하기
*
* 접근 방법: 비트연산 / ALU 연산을 사용하여 두 수의 합을 구함
* 1. XOR 연산을 사용하여 두 수의 합을 구함 (올림수는 제외)
* 2. AND 연산과 왼쪽 시프트를 사용하여 올림수를 구함
* 3. 위의 두 과정을 올림수가 0이 될 때까지 반복
*
* 시간복잡도: O(log max(a,b)), 최대 32번 반복
* 공간복잡도: O(1)
*/
/**
* @param {number} a
* @param {number} b
* @return {number}
*/
var getSum = function (a, b) {
while (b !== 0) {
// XOR: 자리올림 없는 덧셈
let sum = a ^ b;

// AND + 시프트: 자리올림 계산
let carry = (a & b) << 1;

// 다음 반복을 위해 값 업데이트
a = sum;
b = carry;
}

return a;
};