Skip to content
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
25 changes: 25 additions & 0 deletions longest-substring-without-repeating-characters/ICE0208.java

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.

🏷️ 알고리즘 패턴 분석

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)

피드백: 고정 크기 배열과 원소 위치 추적으로 각 문자의 마지막 위치를 기록하고 윈도우를 좌측으로 이동시킨다.

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import java.util.Arrays;

class Solution {
public int lengthOfLongestSubstring(String s) {
int [] lastSeen = new int[128];

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.

문제에서 주어지는 입력값인 아스키 코드 값 범위로 제한두신것이 인상적이네요!
저는 set을 사용했는데 spacial locality, 캐시 히트율 등에서도 배열로 구현한게 더 좋아보입니다.

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;
}
}
89 changes: 89 additions & 0 deletions number-of-islands/ICE0208.java

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.

🏷️ 알고리즘 패턴 분석

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로 표시해 중복 방문을 막는다.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
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;
}
}
17 changes: 17 additions & 0 deletions reverse-linked-list/ICE0208.java

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.

🏷️ 알고리즘 패턴 분석

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)를 사용해 포인터를 역전시킨다.

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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;
}
}
54 changes: 54 additions & 0 deletions set-matrix-zeroes/ICE0208.java

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.

🏷️ 알고리즘 패턴 분석

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)

피드백: 입력 행렬에 추가적인 공간을 사용하지 않고, 첫 행/열을 마커로 활용하여 전체 원소를 한 번씩 스캔한다.

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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;
}
}
}
}
48 changes: 48 additions & 0 deletions unique-paths/ICE0208.java

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.

🏷️ 알고리즘 패턴 분석

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를 통해 메모리 사용량을 줄였고, 각 행마다 이전 열의 값을 이용해 현재 값을 업데이트한다.

개선 제안: 추가적으로 커스텀 최적화나 초기화 로직을 간소화해도 좋다.

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

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.

🏷️ 알고리즘 패턴 분석

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 배열 버전은 공간을 절약합니다.

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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];
}
Comment on lines +3 to +16

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.

깔끔하게 잘 풀여주셨네요. 다만 시간, 공간복잡도 분석을 주석에 같이 써주시면 좋을 것 같습니다!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

리뷰 감사합니다!  🙌
다음주 문제풀이부터는 주석에 복잡도도 분석해서 남겨놓아야겠네요 🫡


/**
* 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];
}
}
Loading