[ICE0208] WEEK 07 Solutions - #2798
Conversation
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
number-of-islands/ICE0208.java
import java.util.ArrayDeque;
import java.util.Deque;
class Solution {
private static final int[][] DIRECTIONS = {
{0, 1},
{0, -1},
{1, 0},
{-1, 0}
};
private static final char WATER = '0';
private static final char LAND = '1';
private record Position(int row, int column) {
}
/**
* 격자를 순회하며 아직 방문하지 않은 육지를 발견할 때마다
* 연결된 하나의 섬을 반복형 DFS로 모두 방문 처리한다.
*
* 시간 복잡도: O(m * n)
* 공간 복잡도: O(m * n)
*/
public int numIslands(char[][] grid) {
int islandCount = 0;
for (int row = 0; row < grid.length; row++) {
for (int column = 0; column < grid[row].length; column++) {
if (grid[row][column] != LAND) {
continue;
}
// 아직 방문하지 않은 육지는 새로운 섬의 시작점이다.
islandCount++;
markIslandAsVisited(grid, row, column);
}
}
return islandCount;
}
/**
* 시작 위치와 상하좌우로 연결된 모든 육지를 방문 처리한다.
* 재귀 호출로 인한 스택 오버플로를 피하기 위해 별도의 스택을 사용한다.
*/
private static void markIslandAsVisited(
char[][] grid,
int startRow,
int startColumn
) {
Deque<Position> stack = new ArrayDeque<>();
stack.push(new Position(startRow, startColumn));
// 스택에 넣는 시점에 방문 처리하여 같은 위치가 중복으로 들어가는 것을 방지한다.
grid[startRow][startColumn] = WATER;
while (!stack.isEmpty()) {
Position current = stack.pop();
for (int[] direction : DIRECTIONS) {
int nextRow = current.row() + direction[0];
int nextColumn = current.column() + direction[1];
if (!isInBounds(grid, nextRow, nextColumn)) {
continue;
}
if (grid[nextRow][nextColumn] != LAND) {
continue;
}
grid[nextRow][nextColumn] = WATER;
stack.push(new Position(nextRow, nextColumn));
}
}
}
private static boolean isInBounds(
char[][] grid,
int row,
int column
) {
return row >= 0
&& row < grid.length
&& column >= 0
&& column < grid[0].length;
}
}- 패턴: Depth-First Search, Stack/Explicit Stack (as part of DFS)
- 설명: 그리드를 순회하며 섬(연결된 육지)을 DFS로 방문 처리한다. 재귀 대신 스택을 사용해 DFS를 구현하고, 인접한 육지를 탐색하며 방문 표식을 남긴다.
📊 시간/공간 복잡도 분석
| 유저 분석 | 실제 분석 | 결과 | |
|---|---|---|---|
| Time | O(m * n) | O(n * m) | ❌ |
| Space | O(m * n) | O(n * m) | ❌ |
피드백: 두 방향으로의 인접만 확장하는 DFS로 전체 격자를 한 번씩 방문하고, 방문 시 육지를 WATER로 표시해 중복 방문을 막는다.
개선 제안: 현재 구현이 적절해 보입니다.
📊 ICE0208 님의 학습 현황이번 주 제출 문제
누적 학습 요약
문제 풀이 현황
🤖 이 댓글은 GitHub App을 통해 자동으로 작성되었습니다. 🔢 API 사용량 (gpt-5-nano)
|
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
unique-paths/ICE0208.java
import java.util.Arrays;
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
// 첫 번째 행과 열은 한 방향으로만 이동해 도달할 수 있으므로 1로 초기화합니다.
initializeBaseCases(dp);
for (int row = 1; row < m; ++row) {
for (int col = 1; col < n; ++col) {
dp[row][col] = dp[row - 1][col] + dp[row][col - 1];
}
}
return dp[m - 1][n - 1];
}
/**
* DP 배열의 첫 번째 행과 첫 번째 열을 1로 초기화합니다.
* @param dp 초기화할 DP 배열
*/
private static void initializeBaseCases(int[][] dp) {
int rows = dp.length;
int cols = dp[0].length;
for (int row = 0; row < rows; row++) {
dp[row][0] = 1;
}
for (int col = 0; col < cols; col++) {
dp[0][col] = 1;
}
}
}
class Solution2 {
public int uniquePaths(int m, int n) {
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int row = 1; row < m; ++row) {
for (int col = 1; col < n; ++col) {
dp[col] += dp[col - 1];
}
}
return dp[n - 1];
}
}- 패턴: Dynamic Programming, Greedy
- 설명: 코드는 두 가지 방식으로 경로의 수를 DP로 계산합니다. 2D 배열과 1D 배열 모두에서 현재 위치의 경로 수를 위/좌의 합으로 갱신하므로 DP 패턴에 속합니다. 주어진 문제의 해를 작은 부분 문제로 나누어 해결하는 특징이 명확합니다.
📊 시간/공간 복잡도 분석
ℹ️ 이 파일에는 2가지 풀이가 포함되어 있어 각각 분석합니다.
풀이 1: Solution.uniquePaths — Time: O(m*n) / Space: O(m*n)
| 복잡도 | |
|---|---|
| Time | O(m*n) |
| Space | O(m*n) |
피드백: 2차원 DP 배열을 사용해 이웃 셀의 합으로 현재 셀의 경로 수를 계산합니다.
개선 제안: 현재 구현은 명확하지만 메모리 사용을 줄이려면 공간 최적화 버전(1차원 DP)을 사용할 수 있습니다.
풀이 2: Solution2.uniquePaths — Time: O(m*n) / Space: O(n)
| 복잡도 | |
|---|---|
| Time | O(m*n) |
| Space | O(n) |
피드백: 1차원 배열 dp를 통해 메모리 사용량을 줄였고, 각 행마다 이전 열의 값을 이용해 현재 값을 업데이트한다.
개선 제안: 추가적으로 커스텀 최적화나 초기화 로직을 간소화해도 좋다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
unique-paths/ICE0208.java
import java.util.Arrays;
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
// 첫 번째 행과 열은 한 방향으로만 이동해 도달할 수 있으므로 1로 초기화합니다.
initializeBaseCases(dp);
for (int row = 1; row < m; ++row) {
for (int col = 1; col < n; ++col) {
dp[row][col] = dp[row - 1][col] + dp[row][col - 1];
}
}
return dp[m - 1][n - 1];
}
/**
* DP 배열의 첫 번째 행과 첫 번째 열을 1로 초기화합니다.
* @param dp 초기화할 DP 배열
*/
private static void initializeBaseCases(int[][] dp) {
int rows = dp.length;
int cols = dp[0].length;
for (int row = 0; row < rows; row++) {
dp[row][0] = 1;
}
for (int col = 0; col < cols; col++) {
dp[0][col] = 1;
}
}
}
class Solution2 {
public int uniquePaths(int m, int n) {
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int row = 1; row < m; ++row) {
for (int col = 1; col < n; ++col) {
dp[col] += dp[col - 1];
}
}
return dp[n - 1];
}
}- 패턴: Dynamic Programming, Monotonic Stack
- 설명: 두 가지 풀이 모두 DP를 이용한 경로 수 계산 패턴으로 각 셀의 경로 수를 합산합니다. 또한 두 번째 풀이에서 1차원 DP 배열로 공간 최적화하는 일반적 DP 패턴이 보입니다.
📊 시간/공간 복잡도 분석
| 복잡도 | |
|---|---|
| Time | O(m*n) |
| Space | O(m*n) |
피드백: 2D 배열 버전은 직관적이고 이해하기 쉽고, 1D 배열 버전은 공간을 절약합니다.
개선 제안: 현재 구현이 적절해 보입니다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
set-matrix-zeroes/ICE0208.java
import java.util.Arrays;
class Solution {
public void setZeroes(int[][] matrix) {
int rows = matrix.length;
int cols = matrix[0].length;
// 첫 번째 행과 열은 marker로 사용하므로, 원래 0이 있었는지 별도로 저장한다.
boolean firstRowHasZero = false;
for (int col = 0; col < cols; ++col) {
if (matrix[0][col] == 0) {
firstRowHasZero = true;
break;
}
}
boolean firstColumnHasZero = false;
for (int row = 0; row < rows; ++row) {
if (matrix[row][0] == 0) {
firstColumnHasZero = true;
break;
}
}
// 첫 번째 행과 열을 각 행과 열의 zero marker로 사용한다.
for (int row = 1; row < rows; ++row) {
for (int col = 1; col < cols; ++col) {
if (matrix[row][col] == 0) {
matrix[row][0] = 0;
matrix[0][col] = 0;
}
}
}
// marker를 기준으로 내부 원소를 0으로 변경한다.
for (int row = 1; row < rows; ++row) {
for (int col = 1; col < cols; ++col) {
if (matrix[row][0] == 0 || matrix[0][col] == 0) {
matrix[row][col] = 0;
}
}
}
if (firstRowHasZero) {
Arrays.fill(matrix[0], 0);
}
if (firstColumnHasZero) {
for (int row = 0; row < rows; ++row) {
matrix[row][0] = 0;
}
}
}
}- 패턴: Greedy, Dynamic Programming, Divide and Conquer, Two Pointers, Hash Map / Hash Set, Binary Search, Monotonic Stack, Heap / Priority Queue, DFS, BFS, Backtracking, Union Find, Trie, Bit Manipulation, Sliding Window
- 설명: 이 코드는 2D 매트릭스에서 특정 행/열을 마커로 이용해 제로를 확산시키는 문제로, 공간을 추가로 사용하지 않고 기존 행/열을 마커로 재활용하는 방식이 핵심이다. 제한된 공간에서 상태를 저장하고 조건에 따라 원소를 업데이트하는 아이디어는 다수의 공간 최적화 패턴과 연관된다.
📊 시간/공간 복잡도 분석
| 복잡도 | |
|---|---|
| Time | O(m * n) |
| Space | O(1) |
피드백: 입력 행렬에 추가적인 공간을 사용하지 않고, 첫 행/열을 마커로 활용하여 전체 원소를 한 번씩 스캔한다.
개선 제안: 현재 구현이 적절해 보입니다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
longest-substring-without-repeating-characters/ICE0208.java
import java.util.Arrays;
class Solution {
public int lengthOfLongestSubstring(String s) {
int [] lastSeen = new int[128];
Arrays.fill(lastSeen, -1);
int left = 0;
int maxLength = 0;
for (int right = 0; right < s.length(); right++) {
char current = s.charAt(right);
// 현재 문자가 이미 등장했다면
// 이전 등장 위치 다음으로 left를 이동한다.
// left는 right 보다 작거나 같다는 것이 보장.
left = Math.max(left, lastSeen[current] + 1);
lastSeen[current] = right;
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}
}- 패턴: Two Pointers, Hash Map / Hash Set, Sliding Window
- 설명: 문자열에서 서로 다른 부분문자열의 길이를 왼쪽과 오른쪽 포인터로 탐색하며, 각 문자의 마지막 등장 위치를 저장해 중복을 제거하는 슬라이딩 윈도우 패턴의 구현이다.
📊 시간/공간 복잡도 분석
| 복잡도 | |
|---|---|
| Time | O(n) |
| Space | O(1) |
피드백: 고정 크기 배열과 원소 위치 추적으로 각 문자의 마지막 위치를 기록하고 윈도우를 좌측으로 이동시킨다.
개선 제안: 현재 구현이 적절해 보입니다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
reverse-linked-list/ICE0208.java
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode current = head;
while (current != null) {
ListNode next = current.next;
current.next = prev;
prev = current;
current = next;
}
return prev;
}
}- 패턴: Two Pointers, Linked List
- 설명: 두 포인터(prev, current)로 단일 연결 리스트를 뒤집는 전형적인 패턴으로 두 포인터를 이용해 노드 연결 방향을 역전시킨다. 반복문으로 노드를 순회하며 인접한 관계를 재설정한다.
📊 시간/공간 복잡도 분석
| 복잡도 | |
|---|---|
| Time | O(n) |
| Space | O(1) |
피드백: 커서 변수들(prev, current, next)를 사용해 포인터를 역전시킨다.
개선 제안: 현재 구현이 적절해 보입니다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
|
|
||
| class Solution { | ||
| public int lengthOfLongestSubstring(String s) { | ||
| int [] lastSeen = new int[128]; |
There was a problem hiding this comment.
문제에서 주어지는 입력값인 아스키 코드 값 범위로 제한두신것이 인상적이네요!
저는 set을 사용했는데 spacial locality, 캐시 히트율 등에서도 배열로 구현한게 더 좋아보입니다.
| class Solution { | ||
| public int uniquePaths(int m, int n) { | ||
| int[][] dp = new int[m][n]; | ||
| // 첫 번째 행과 열은 한 방향으로만 이동해 도달할 수 있으므로 1로 초기화합니다. | ||
| initializeBaseCases(dp); | ||
|
|
||
| for (int row = 1; row < m; ++row) { | ||
| for (int col = 1; col < n; ++col) { | ||
| dp[row][col] = dp[row - 1][col] + dp[row][col - 1]; | ||
| } | ||
| } | ||
|
|
||
| return dp[m - 1][n - 1]; | ||
| } |
There was a problem hiding this comment.
깔끔하게 잘 풀여주셨네요. 다만 시간, 공간복잡도 분석을 주석에 같이 써주시면 좋을 것 같습니다!
There was a problem hiding this comment.
리뷰 감사합니다! 🙌
다음주 문제풀이부터는 주석에 복잡도도 분석해서 남겨놓아야겠네요 🫡
답안 제출 문제
작성자 체크 리스트
In Review로 설정해주세요.검토자 체크 리스트
Important
본인 답안 제출 뿐만 아니라 다른 분 PR 하나 이상을 반드시 검토를 해주셔야 합니다!