-
-
Notifications
You must be signed in to change notification settings - Fork 246
[jinvicky] Week7 solution #1878
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import java.util.HashSet; | ||
import java.util.Set; | ||
|
||
public class Solution { | ||
/** | ||
* 문제에서 말하는 pos는 설명을 돕는 사이클 발생 위치이지 코드 상에서 사용하지 않는다. | ||
*/ | ||
public boolean hasCycle(ListNode head) { | ||
Set<Integer> 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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Character, Integer> 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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 섬의 면접 값을 반환하기 위해 반환 값의 타입을 int로 하신건가요? 만약 제가 이해한게 맞다면, 면적 값을 구하는 함수라면 dfsByInt 라는 이름보다는 다름 이름으로 작성하는건 어떨 까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 말씀하신 점이 맞습니다. 이번 dfs 문제 초심자로서 dfsByInt처럼 값을 누적해서 반환하는 경우와 dfs처럼 void 반환 타입으로 호출만 하는 경우를 구분해서 학습하려고 작성했습니다. 그렇다 보니 메서드 가독성이 안 좋아졌네요 하하;; 다음주에는 조언대로 더 이름을 문제에 맞게 개선해보겠습니다. 감사합니다! |
||
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<Node,Integer> 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; | ||
* } | ||
*/ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import java.util.Stack; | ||
|
||
class Solution { | ||
/** | ||
* 리버스와 동일한 후입선출 구조를 생각했고 그래서 자료구조로 스택을 결정했다. | ||
* 사이클 문제를 염두해 두고 조건을 설계했지만 사이클을 끊는 위치가 틀려서 오래 걸렸다. | ||
*/ | ||
public ListNode reverseList(ListNode head) { | ||
if (head == null) return null; | ||
|
||
Stack<ListNode> 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; | ||
} | ||
|
||
/** | ||
* 포인터 반복문을 새로 배웠을 때 fast/slow와 같은 투 포인터 알고리즘과 헷갈렸지만 둘은 전혀 다르다. | ||
* 포인터 반복문 | ||
* * 링크 방향 뒤집기 | ||
* * 3개 포인터 (prev, cur, next) | ||
* * 리스트 자체 구조 변경 | ||
* <p> | ||
* 투 포인터 | ||
* * 중간, 사이클, 교차점 등 탐색 | ||
* * 2개 포인터 (slow, fast) | ||
* * 리스트 구조 그대로, 위치 정보만 얻음 | ||
* <p> | ||
* 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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Java에서 배열을 생성할 때, 초기화 값을 넣어 주는 방법이 있을까요? JavaScript에서는 배열을 생성하면서 초기 값을 설정할 수 있는 방법이 있어 궁금해서 질문 남깁니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 자바는 Arrays.fill()로 선언한 배열에 초기화 값을 넣을 수 있습니다. 또한 이미 선언한 배열을 채우는 것이지 선언과 초기화를 동시에 하지는 못합니다. 감사합니다:) <예시> |
||
for (int i = 0; i < m; i++) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
풀이를 2개를 해주셨군요! 다양한 풀이를 알려주셔서 감사합니다.