Skip to content

[clara-shin] WEEK07 Solutions #1467

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 7 commits into from
May 16, 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
42 changes: 42 additions & 0 deletions longest-substring-without-repeating-characters/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* 중복 문자 없이 가장 긴 부분 문자열의 길이를 찾기 (부분 문자열은 연속된 문자들의 집합이어야 함)
*
* 슬라이딩 윈도우(Sliding Window) 기법
* 접근 방법:
* 1. 시작포인터(left)와 끝포인터(right)를 사용하여 윈도우 정의
* 2. 문자의 등장여부는 Map을 사용하여 추적
* 3. 끝 포인터를 이동시키면서 윈도우 확장
* 4. 중복 문자가 발견되면 시작 포인터를 이동시켜 윈도우 축소
* 5. 최대 길이를 업데이트
*
* 시간복잡도: O(n), 공간복잡도: O(min(n, m)) (n: 문자열 길이, m: 문자 집합 크기)
*/
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function (s) {
if (!s.length) return 0;

let maxLength = 0;
let left = 0; // 시작 포인터(윈도우 왼쪽 경계)
const charMap = new Map();

for (let right = 0; right < s.length; right++) {
const currentChar = s[right]; // 현재 문자

// 현재문자가 이미 Map에 있고, 그 인덱스(위치)가 현재 윈도우 내에 있다면
if (charMap.has(currentChar) && charMap.get(currentChar) >= left) {
// 시작포인터(윈도우 왼쪽 경계)를 중복된 문자의 다음위치로 이동
left = charMap.get(currentChar) + 1;
}

// 현재 문자의 인덱스를 Map에 저장
charMap.set(currentChar, right);

// 현재 윈도우의 길이와 기존 최대 길이 중 큰 값을 선택
// right - left + 1: 현재 윈도우의 길이
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength; // 최대 길이 리턴
};
87 changes: 87 additions & 0 deletions number-of-islands/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* 섬의 개수 찾기 - 그래프 탐색 문제
* 2차원 바이너리 그리드, 1은 섬, 0은 물
* 그리드의 모든 가장자리는 물로 둘러싸여 있음
*
* 문제 접근: BFS(너비 우선 탐색) 사용
* 1. 그리드를 순회하면서 땅(1)을 발견하면 BFS로 연결된 모든 땅을 탐색
* 2. BFS 탐색 중에 방문한 땅은 방문 배열에 표시
* 3. BFS 탐색이 끝나면 섬의 개수를 증가
* 4. 그리드 전체를 순회하면서 섬의 개수를 세기
*
* BFS 선택 이유: 제약 조건(그리드 크기 최대 300x300)을 고려했을 때,
* - 그리드가 커질수록 DFS는 재귀 호출로 인한 스택 오버플로우 위험이 있음,
* - BFS는 큐를 사용하여 이 위험을 피할 수 있음
* - 방문배열을 만들어서 원본데이터 보존(불변성 유지)
* - 단, BFS는 큐를 사용하므로 메모리 사용량이 더 많을 수 있음
*
* 시간 복잡도: O(M×N) (M: 행의 개수, N: 열의 개수)
* 공간 복잡도: O(M×N) (방문 배열과 큐를 사용)
*/
/**
* @param {character[][]} grid
* @return {number}
*/
var numIslands = function (grid) {
if (!grid || grid.length === 0) {
return 0;
}

const rows = grid.length;
const cols = grid[0].length;
let islandCount = 0;

// 방문 배열 생성
const visited = Array(rows)
.fill()
.map(() => Array(cols).fill(false));

// 방향 배열 (상, 하, 좌, 우)
const directions = [
[-1, 0],
[1, 0],
[0, -1],
[0, 1],
];

function bfs(startRow, startCol) {
const queue = [[startRow, startCol]];
visited[startRow][startCol] = true; // 시작점 방문 처리

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

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

// 범위 안에서, 방문하지 않은 땅('1')이면 탐색
if (
newRow >= 0 &&
newRow < rows &&
newCol >= 0 &&
newCol < cols &&
grid[newRow][newCol] === '1' &&
!visited[newRow][newCol]
) {
queue.push([newRow, newCol]);
visited[newRow][newCol] = true; // 방문 처리
}
}
}
}

// 그리드 전체 순회
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
// 땅('1')을 발견하고 아직 방문하지 않았으면 BFS 시작 및 섬 카운트 증가
if (grid[i][j] === '1' && !visited[i][j]) {
islandCount++;
bfs(i, j);
}
}
}

return islandCount;
};
46 changes: 46 additions & 0 deletions reverse-linked-list/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* 단일 연결리스트(Singly Linked List)의 노드 순서를 반대로 뒤집어서 리턴하기
* follow-up: 연결리스트는 반복적(Iterative) 또는 재귀적(Recursive)으로 뒤집을 수 있는데, 두 가지 방법 다 가능?
*
* 반복문(Iterative) 방식 (✅ 실무에서 더 많이 사용한다고 함)
* 포인터 세 개(prev, curr, next)를 사용, 리스트를 한 번 순회하며 역방향으로 연결을 바꿈
* 시간복잡도: O(n), 공간복잡도: O(1)
*
* 재귀(Recursive) 방식
* 재귀적으로 끝까지 들어간 뒤, 각 노드의 next를 역방향으로 연결
* 시간복잡도: O(n), 공간복잡도: O(n) (재귀 콜스택)
*/

/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
// 반복문(Iterative) 방식
var reverseList = function (head) {
let prev = null;
let curr = head;
while (curr) {
const next = curr.next; // 다음 노드 저장
curr.next = prev; // 현재 노드의 next를 prev로 변경
prev = curr; // prev를 현재 노드로 이동
curr = next; // curr를 다음 노드로 이동
}
return prev;
};

// 재귀(Recursive) 방식
var reverseList = function (head) {
if (!head || !head.next) return head; // 빈 리스트 또는 마지막 노드인 경우, 빠른 리턴

const reversed = reverseList(head.next); // 나머지 리스트 역순
head.next.next = head; // 다음 노드의 next를 현재 노드로
head.next = null; // 현재 노드의 next를 null로
return reversed;
};
71 changes: 71 additions & 0 deletions set-matrix-zeroes/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* 행렬에서 0을 찾아, 해당 요소의 전체 행과 열을 0으로 변환하는 문제
*
* 팔로우업: O(1) 공간 복잡도로 해결하기
* - 원래 행렬의 첫번째 행과 열을 사용, 0이 있는 행과 열을 표시(추가 공간사용 없이)
* 접근 방법:
* 1. 첫번째 행과 열에 0이 있는지 확인, 있다면 별도의 변수에 저장
* 2. 첫번째 행과 열을 제외한 나머지 행렬을 순회 ➡️ 0을 발견하면 해당 위치의 행과 열을 0으로 설정
* 3. 설정된 마커 기반으로 내부 행렬의 요소를 0으로 업데이트
*
* 시간복잡도: O(m*n) ➡️ 행렬을 두번 순회
* 공간복잡도: O(1) ➡️ 추가 배열 사용 안함
*/
/**
* @param {number[][]} matrix
* @return {void} Do not return anything, modify matrix in-place instead.
*/
var setZeroes = function (matrix) {
if (!matrix || matrix.length === 0 || matrix[0].length === 0) return;

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

// 한번의 반복문으로 첫번째 행과 열을 체크
let firstRowHasZero = false;
let firstColHasZero = false;

// 첫번째 행 체크
for (let j = 0; j < n; j++) {
if (matrix[0][j] === 0) {
firstRowHasZero = true;
break;
}
}
// 첫번째 열 체크
for (let i = 0; i < m; i++) {
if (matrix[i][0] === 0) {
firstColHasZero = true;
break;
}
}

// 첫번째 행과 열을 제외한 나머지 행렬을 순회, 마커 설정
for (let i = 1; i < m; i++) {
for (let j = 1; j < n; j++) {
if (matrix[i][j] === 0) {
matrix[i][0] = 0; // 해당 행의 첫번째 열을 0으로 마킹
matrix[0][j] = 0; // 해당 열의 첫번째 행을 0으로 마킹
}
}
}
// 마커를 기반으로 내부 행렬의 요소를 0으로 업데이트
for (let i = 1; i < m; i++) {
for (let j = 1; j < n; j++) {
if (matrix[i][0] === 0 || matrix[0][j] === 0) {
matrix[i][j] = 0;
}
}
}
// 첫번째 행과 열을 업데이트
if (firstRowHasZero) {
for (let j = 0; j < n; j++) {
matrix[0][j] = 0;
}
}
if (firstColHasZero) {
for (let i = 0; i < m; i++) {
matrix[i][0] = 0;
}
}
};
36 changes: 36 additions & 0 deletions unique-paths/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* DP로 풀 수도 있고, 조합으로 풀 수도 있음
*
* 조합: (m+n-2)C(n-1)
* n개의 원소 중에서 r개를 선택하는 경우의 수 (순서고려 안함)
* nCr = n! / (r!(n-r)!)
* 장점: 격자의 각 위치까지 경로의 수를 모두 알수 있어서 유연함, DP보다 시간복잡도 낮음
* 단점: 큰 수의 팩토리얼을 계산해야 하므로 오버플로우가 발생할 수 있음
*
* 시간복잡도: O(min(m,n)) ➡️ min(m-1, n-1)까지 반복하는 루프 때문에
* 공간복잡도: O(1) ➡️ 추가 배열 사용 안함, 단일변수만 사용
*/
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function (m, n) {
// 총 이동 횟수: 오른쪽 (n-1)번 + 아래쪽 (m-1)번 = m+n-2
let N = m + n - 2;

// 아래쪽으로 이동하는 횟수 또는 오른쪽으로 이동하는 횟수 중 더 작은 값 선택
// 조합 공식에서 nCk = nCn-k 특성을 이용해 계산을 최적화
let k = Math.min(m - 1, n - 1);

// 결과 변수를 초기화
let result = 1;

// 조합 계산: nCk = (n * (n-1) * ... * (n-k+1)) / (k * (k-1) * ... * 1) 방식으로 계산
for (let i = 1; i <= k; i++) {
result *= N - (i - 1); // 분자 부분: n, n-1, n-2, ..., n-k+1
result /= i; // 분모 부분: k, k-1, k-2, ..., 1
}

return result;
};