From 4fab8abdc93deb602251b4d0f65a5e9da0a249b0 Mon Sep 17 00:00:00 2001 From: jinvicky Date: Mon, 1 Sep 2025 10:12:01 +0900 Subject: [PATCH 1/5] reverse-linked-list solution --- reverse-linked-list/jinvicky.java | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 reverse-linked-list/jinvicky.java diff --git a/reverse-linked-list/jinvicky.java b/reverse-linked-list/jinvicky.java new file mode 100644 index 000000000..c025e541d --- /dev/null +++ b/reverse-linked-list/jinvicky.java @@ -0,0 +1,27 @@ +import java.util.Stack; + +class Solution { + /** + * 리버스와 동일한 후입선출 구조를 생각했고 그래서 자료구조로 스택을 결정했다. + * 사이클 문제를 염두해 두고 조건을 설계했지만 사이클을 끊는 위치가 틀려서 오래 걸렸다. + */ + public ListNode reverseList(ListNode head) { + if (head == null) return null; + + Stack stack = new Stack<>(); + while (head != null) { + stack.push(head); + head = head.next; + } + + ListNode newHead = stack.pop(); // 정답 반환용 + ListNode current = newHead; // 포인터를 갱신하면서 계산하기용 (소위 더미 포인터) + + while (!stack.isEmpty()) { + current.next = stack.pop(); + current = current.next; // ← 수정: 자기 자신 가리키는 대신 앞으로 이동 + } + current.next = null; // 마지막 tail 정리 + return newHead; + } +} From 21c6a7061b6a90119238c3fc2ff9f8d0e47db314 Mon Sep 17 00:00:00 2001 From: jinvicky Date: Mon, 1 Sep 2025 10:56:51 +0900 Subject: [PATCH 2/5] =?UTF-8?q?reverse-linked-list=20solution,=20=EB=B0=98?= =?UTF-8?q?=EB=B3=B5=EB=AC=B8=20=ED=8F=AC=EC=9D=B8=ED=84=B0=EC=99=80=20?= =?UTF-8?q?=EC=9E=AC=EA=B7=80=20=EC=BC=80=EC=9D=B4=EC=8A=A4=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- reverse-linked-list/jinvicky.java | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/reverse-linked-list/jinvicky.java b/reverse-linked-list/jinvicky.java index c025e541d..4344bb775 100644 --- a/reverse-linked-list/jinvicky.java +++ b/reverse-linked-list/jinvicky.java @@ -24,4 +24,40 @@ public ListNode reverseList(ListNode head) { current.next = null; // 마지막 tail 정리 return newHead; } + + /** + * 포인터 반복문을 새로 배웠을 때 fast/slow와 같은 투 포인터 알고리즘과 헷갈렸지만 둘은 전혀 다르다. + * 포인터 반복문 + * * 링크 방향 뒤집기 + * * 3개 포인터 (prev, cur, next) + * * 리스트 자체 구조 변경 + *

+ * 투 포인터 + * * 중간, 사이클, 교차점 등 탐색 + * * 2개 포인터 (slow, fast) + * * 리스트 구조 그대로, 위치 정보만 얻음 + *

+ * https://bcp0109.tistory.com/142 + */ + public ListNode reverseListByPointer(ListNode head) { + ListNode prev = null; + ListNode cur = head; + while (cur != null) { + ListNode next = cur.next; // next라는 temp 변수를 사용해서 prev와 cur.next 값을 바꾼다. + cur.next = prev; + prev = cur; + cur = next; + // 대각선으로 / / / / 으로 변수명 암기하기 + } + return prev; + } + + public ListNode reverseListByRecursion(ListNode head) { + // head.next가 null인지도 확인하는 로직이 필요합니다. (nullPointerException 방지) + if (head == null || head.next == null) return null; + ListNode newHead = reverseListByRecursion(head.next); + head.next.next = head; + head.next = null; + return newHead; + } } From d6a2539532d1df8f9735ac8000f37e1629971c04 Mon Sep 17 00:00:00 2001 From: jinvicky Date: Mon, 1 Sep 2025 18:12:17 +0900 Subject: [PATCH 3/5] set zeros solution --- .../jinvicky.java | 33 +++++ number-of-islands/jinvicky.java | 128 ++++++++++++++++ reverse-linked-list/jinvicky.java | 2 +- set-matrix-zeroes/jinvicky.java | 137 ++++++++++++++++++ unique-paths/jinvicky.java | 2 + 5 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 longest-substring-without-repeating-characters/jinvicky.java create mode 100644 number-of-islands/jinvicky.java create mode 100644 set-matrix-zeroes/jinvicky.java diff --git a/longest-substring-without-repeating-characters/jinvicky.java b/longest-substring-without-repeating-characters/jinvicky.java new file mode 100644 index 000000000..bde8fd76e --- /dev/null +++ b/longest-substring-without-repeating-characters/jinvicky.java @@ -0,0 +1,33 @@ +import java.util.HashMap; +import java.util.Map; + +class Solution { + /** + * 초기에 중복체크를 위해 HashSet으로 풀었다가 예외 케이스가 발생해서 이전 문자의 인덱스 위치를 알아야 함을 깨닫고 + * HashMap으로 문자:인덱스 쌍을 저장했다. + * 자료구조와 반복문의 흐름은 맞았으나 중복 발견 시 left를 Math.max()로 2가지 케이스를 모두 비교하지 않아서 문제 발생 + * 기존 left와 해당 문자의 마지막 인덱스 중 더 큰값을 선택한다. + */ + public int lengthOfLongestSubstring(String s) { + if (s == null || s.isEmpty()) return 0; + + Map last = new HashMap<>(); + int left = 0, maxLen = 0; + + for (int right = 0; right < s.length(); right++) { + char ch = s.charAt(right); + + // VIP:: left를 오른쪽으로 밀거나(left+=) left를 유지한다. + // 왜 left를 가능한 한 오른쪽으로 업데이트할까? -> 그래야 중복 제거된 온전한 슬라이딩 윈도우를 유지할 수 있기 때문이다. + // abba의 경우 두번째 b, 두번째 a가 이에 해당한다. + // last(b) = max(0, 1+1=2) -> 2 + // last(a) = max(2, 0+1=1) -> 2 + if (last.containsKey(ch)) { + left = Math.max(left, last.get(ch) + 1); + } + maxLen = Math.max(maxLen, right - left + 1); + last.put(ch, right); // 방금 문자의 '마지막 위치' 갱신 + } + return maxLen; + } +} diff --git a/number-of-islands/jinvicky.java b/number-of-islands/jinvicky.java new file mode 100644 index 000000000..2a1718343 --- /dev/null +++ b/number-of-islands/jinvicky.java @@ -0,0 +1,128 @@ +public class jinvicky { +} + +/** + * 일관성을 위해 모든 변수들을 메서드 내 lv로 선언해서 푸는 것으로 한다. + * dfs를 설계할 때 특히 반환 타입에 약한데 void인가 int,booelan 등 기타 타입인가를 확실히 하기 위해 별도 예제로 풀었다. + * 또한 의도적으로 기존 grid를 1 -> 0으로 바꾸어 섬을 없애지 않고 공간복잡도가 증가하더라도 학습을 위해 방문 배열을 별도로 선언해 방문 여부를 체크했다. + */ +class Solution { + public int numIslands1(char[][] grid) { + if (grid == null || grid.length == 0) return 0; + int rows = grid.length, cols = grid[0].length; + boolean[][] visited = new boolean[rows][cols]; + + int count = 0; + for (int r = 0; r < rows; r++) { + for (int c = 0; c < cols; c++) { + if (grid[r][c] == '1' && !visited[r][c]) { + int size = dfsByInt(grid, visited, r, c, rows, cols); + if (size > 0) count++; // 크기가 양수면 섬 1개 + } + } + } + return count; + } + + // dfs는 해당 섬의 넓이를 반환 + private int dfsByInt(char[][] grid, boolean[][] visited, int r, int c, int rows, int cols) { + if (r < 0 || c < 0 || r >= rows || c >= cols) return 0; // 범위를 벗어났다. + if (grid[r][c] == '0' || visited[r][c]) return 0; // 문제의 조건에 맞지 않거나, 이미 방문했다. + + visited[r][c] = true; // 방문 처리 + int area = 1; + + area += dfsByInt(grid, visited, r + 1, c, rows, cols); + area += dfsByInt(grid, visited, r - 1, c, rows, cols); + area += dfsByInt(grid, visited, r, c + 1, rows, cols); + area += dfsByInt(grid, visited, r, c - 1, rows, cols); + + return area; + } + + public int numIslands(char[][] grid) { + if (grid == null || grid.length == 0) return 0; + int rows = grid.length, cols = grid[0].length; + + boolean[][] visited = new boolean[rows][cols]; + int count = 0; + + for (int r = 0; r < rows; r++) { + for (int c = 0; c < cols; c++) { + if (grid[r][c] == '1' && !visited[r][c]) { + dfs(grid, visited, r, c, rows, cols); + count++; // 섬 하나 탐색 끝나면 +1 + } + } + } + return count; + } + + private void dfs(char[][] grid, boolean[][] visited, int r, int c, int rows, int cols) { + if (r < 0 || c < 0 || r >= rows || c >= cols) return; // 그리드 범위를 벗어나면 종료 + if (grid[r][c] == '0' || visited[r][c]) return; // 물이거나 이미 방문한 섬이라면 종료 + + visited[r][c] = true; // 방문 처리 + + // 상하좌우 DFS + dfs(grid, visited, r + 1, c, rows, cols); + dfs(grid, visited, r - 1, c, rows, cols); + dfs(grid, visited, r, c + 1, rows, cols); + dfs(grid, visited, r, c - 1, rows, cols); + } + + /** + * void 반환의 dfs면 return; + * int 반환의 dfs면 return 0; + * bool 반환의 dfs면 return false; + * + * 1. 리턴값 없이 넘겨받은 상태만 갱신 + * void dfs(Node node) { + * if (범위 밖 || 조건 불만족 || 이미 방문) return; + * + * 방문 처리(node); + * + * for (이웃 nei : node) { + * dfs(nei); + * } + * } + * + * 2. 최대/최소 값을 정수로 반환 + * int dfs(Node node) { + * if (범위 밖 || 조건 불만족) return 0; + * + * 방문 처리(node); + * + * int result = 1; // 자기 자신 포함 + * for (이웃 nei : node) { + * result += dfs(nei); + * } + * return result; + * } + * 3. 조건 만족 여부 + * boolean dfs(Node node) { + * if (목표 도달) return true; + * if (범위 밖 || 조건 불만족) return false; + * + * 방문 처리(node); + * + * for (이웃 nei : node) { + * if (dfs(nei)) return true; + * } + * return false; + * } + * + * 4. 메모이제이션/DP 결합 + * int dfs(Node node, Map memo) { + * if (memo.containsKey(node)) return memo.get(node); + * if (기저 조건) return 1; + * + * int best = 0; + * for (이웃 nei : node) { + * best = Math.max(best, 1 + dfs(nei, memo)); + * } + * memo.put(node, best); + * return best; + * } + */ +} diff --git a/reverse-linked-list/jinvicky.java b/reverse-linked-list/jinvicky.java index 4344bb775..79b91ce1b 100644 --- a/reverse-linked-list/jinvicky.java +++ b/reverse-linked-list/jinvicky.java @@ -54,7 +54,7 @@ public ListNode reverseListByPointer(ListNode head) { public ListNode reverseListByRecursion(ListNode head) { // head.next가 null인지도 확인하는 로직이 필요합니다. (nullPointerException 방지) - if (head == null || head.next == null) return null; + if (head == null || head.next == null) return null; // 재귀의 끝, 이제 기존 연산을 취합한다. ListNode newHead = reverseListByRecursion(head.next); head.next.next = head; head.next = null; diff --git a/set-matrix-zeroes/jinvicky.java b/set-matrix-zeroes/jinvicky.java new file mode 100644 index 000000000..ebb3b1620 --- /dev/null +++ b/set-matrix-zeroes/jinvicky.java @@ -0,0 +1,137 @@ +/** + * 대표적인 조건 기반 마킹 문제: 조건을 만족하면 바로 바꾸지 않고, 먼저 표시만 해두고 마지막에 한번에 처리하는 기법 + * 보조 배열을 쓰는 방식이면 2번 전체 탐색이 기본이다. + */ + +/** + * 매트릭스 문제 유형을 알고 easy와 연관 유형을 선행한 다음에 정답을 맞출 수 있었다. + * 1. 조건 기반 갱신/마킹 + * 2. 이웃 집계 + * 3. 행렬 변형 + * 4. 단순 순회 + */ +class Solution { + public void setZeroes(int[][] matrix) { + int[][] assist = new int[matrix.length][matrix[0].length]; + for (int i = 0; i < matrix.length; i++) { + for (int j = 0; j < matrix[0].length; j++) { + if (matrix[i][j] == 0) { + for (int ni = 0; ni < matrix.length; ni++) { + assist[ni][j] = -1; + } + + for (int nj = 0; nj < matrix[0].length; nj++) { // 범위 확인 잘하기 + assist[i][nj] = -1; + } + } + } + } + + for (int i = 0; i < matrix.length; i++) { + for (int j = 0; j < matrix[0].length; j++) { + // assist[i][j]가 -1이면 0으로 바꾼다. + if (assist[i][j] == -1) { + matrix[i][j] = 0; + } + } + } + } + + /** + * 선행 문제들을 모두 풀면 반복할 부분과 응용할 부분을 분리할 수 있었고, 그 부분을 계속 반복학습한 다음에 떼어내서 변형을 시도했다. + */ + // 2차원 그리드의 기초 문제 - 행 단위 합산 후 최대값 찾기 + // https://leetcode.com/problems/richest-customer-wealth/ + public int maximumWealth(int[][] accounts) { + int maxW = 0; + for (int i = 0; i < accounts.length; i++) { + int current = 0; + for (int j = 0; j < accounts[0].length; j++) { + current += accounts[i][j]; + } + maxW = Math.max(maxW, current); + } + return maxW; + } + + // 여기서 invert란 row마다 역순 정렬을 1번 하고, 다시 for문으로 0은 1로, 1은 0으로 바꾸는 것을 의미한다. + // https://leetcode.com/problems/flipping-an-image/ + public int[][] flipAndInvertImage(int[][] image) { + for (int i = 0; i < image.length; i++) { + int left = 0; + int right = image[0].length - 1; + + while (left < right) { + int temp = image[i][left]; + image[i][left] = image[i][right]; + image[i][right] = temp; + + right--; + left++; + } + } + + for (int i = 0; i < image.length; i++) { + for (int j = 0; j < image[0].length; j++) { + image[i][j] = image[i][j] == 0 ? 1 : 0; + } + } + return image; + } + + // grid[i][j]를 grid[j][i]로 바꾸는 문제. grid[2][3] -> grid[3][2] 이중 for문을 어떻게 반영할까? 처음에는 생각이 안 나서 while + for로 했다가 이중 for문으로 작은 리팩토링 + // https://leetcode.com/problems/transpose-matrix/description/ + public int[][] transpose(int[][] matrix) { + int[][] answer = new int[matrix[0].length][matrix.length]; + + for (int j = 0; j < matrix[0].length; j++) { + for (int i = 0; i < matrix.length; i++) { + answer[j][i] = matrix[i][j]; + } + } + return answer; + } + + // 8방향을 도전, dir 배열이 틀리지 않게 하는 게 어려웠다. -> 범위를 벗어났는 지 확인하는 로직이 마치 dfs의 그것이었다. (0보다 작은지, length보다 크거나 같은지 국룰 확인) + // 여기서 알아야 할 점은 입력받은 배열을 수정하는 경우와 assist 배열이 꼭 필요한 경우를 구분해야 한다는 것이다. + // 이 문제는 [i][j]마다 평균을 계산하지만 원본 배열을 바꾸지 않고 assist에 기록한 다음에 그걸 반환해야 한다. (계산은 매번 입력받은 데이터로 하기 때문이다) + // https://leetcode.com/problems/image-smoother/ + public int[][] imageSmoother(int[][] img) { + int[][] dir8 = new int[][]{ + {-1, 0}, + {-1, 1}, + {0, 1}, + {1, 1}, + {1, 0}, + {1, -1}, + {0, -1}, + {-1, -1} + }; + + int[][] assist = new int[img.length][img[0].length]; + + for (int i = 0; i < img.length; i++) { + for (int j = 0; j < img[0].length; j++) { + // dir의 8을 돌리는데 중요한 건 sum, cnt가 칸마다 다르다는 것이다. 그래서 평균을 /8이 아니라 /cnt로 하고 sum도 마찬가지였다. + int sum = img[i][j]; + int cnt = 1; + for (int[] d : dir8) { // 3중 for문 안에서 ni, nj 개념을 setMatrixZeros에서 썼다. + int ni = i + d[0]; + int nj = j + d[1]; + + if (ni < 0 || ni >= img.length || nj < 0 || nj >= img[0].length) { // 범위를 벗어나면 넘어가는 로직을 참고했다. + continue; + } + + sum += img[ni][nj]; + cnt++; + } + assist[i][j] = sum / cnt; + } + } + return assist; + } + + // + // https://leetcode.com/problems/game-of-life/ +} diff --git a/unique-paths/jinvicky.java b/unique-paths/jinvicky.java index a7430fd9e..f738611cb 100644 --- a/unique-paths/jinvicky.java +++ b/unique-paths/jinvicky.java @@ -4,6 +4,8 @@ public int uniquePaths(int m, int n) { * 여기서 이동가능한 경우는 right, down 두가지 경우이다. * 모든 블록은 내 왼쪽 블록에서 나로 온 경우, 내 위 블록에서 나로 온 경우를 고려해서 [i-1][j] + [i][j-1]로 표현할 수 있다. * 단 가로 첫번째 줄과 세로 첫번째 줄은 1로 초기화 해줘야 한다. (왜냐하면 각각 down, right이 없기 때문에 그 블록들은 1가지 경우로밖에 도달할 수 없기 때문이다.) + * + * */ int[][] dp = new int[m][n]; for (int i = 0; i < m; i++) { From d3b86174295ee281837172590afec1176beeb39d Mon Sep 17 00:00:00 2001 From: jinvicky Date: Tue, 2 Sep 2025 10:02:46 +0900 Subject: [PATCH 4/5] linked list cycle solution --- linked-list-cycle/jinvicky.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 linked-list-cycle/jinvicky.java diff --git a/linked-list-cycle/jinvicky.java b/linked-list-cycle/jinvicky.java new file mode 100644 index 000000000..8048b037b --- /dev/null +++ b/linked-list-cycle/jinvicky.java @@ -0,0 +1,18 @@ +import java.util.HashSet; +import java.util.Set; + +public class Solution { + /** + * 문제에서 말하는 pos는 설명을 돕는 사이클 발생 위치이지 코드 상에서 사용하지 않는다. + */ + public boolean hasCycle(ListNode head) { + Set set = new HashSet<>(); + while(head != null) { + // set에서 이미 존재하는 숫자가 있으면 바로 return true; + // set.add() 메서드는 추가 성공 시 true, 이미 존재하는 숫자면 추가하지 못하고 false를 반환한다. + if(!set.add(head.val)) return true; + head = head.next; + } + return false; + } +} \ No newline at end of file From 2ec4863245f679a70433772960933577620c65c8 Mon Sep 17 00:00:00 2001 From: jinvicky Date: Thu, 4 Sep 2025 11:59:49 +0900 Subject: [PATCH 5/5] fix lint --- linked-list-cycle/jinvicky.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linked-list-cycle/jinvicky.java b/linked-list-cycle/jinvicky.java index 8048b037b..45056cf7b 100644 --- a/linked-list-cycle/jinvicky.java +++ b/linked-list-cycle/jinvicky.java @@ -15,4 +15,4 @@ public boolean hasCycle(ListNode head) { } return false; } -} \ No newline at end of file +}