Skip to content
Merged
26 changes: 26 additions & 0 deletions container-with-most-water/jinvicky.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* 구하려는 것은 최대 영역값이기 때문에 x,y축을 계산한 현재 영역과 기존 영역을 비교해 max값을 반환하면 됩니다.
* 포인터를 어떻게 좁힐까 생각할 수 있는데 쉽게 말해서 start와 end 중 더 작은 쪽을 앞 또는 뒤로 이동하면 됩니다.
* 영역은 두 막대가 모두 충족가능한 길이가 되어야 하므로 Math.min()으로 설정합니다.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* 영역은 막대가 모두 충족가능한 길이가 되어야 하므로 Math.min()으로 설정합니다.
* 영역의 높이는 막대가 모두 충족가능한 길이가 되어야 하므로 Math.min()으로 설정합니다.

*/
class Solution {
public int maxArea(int[] height) {
int start = 0;
int end = height.length - 1;
int area = 0;

while (start < end) {
int y = Math.min(height[start], height[end]); // y축은 더 작은 값으로 설정
int x = Math.abs(start - end);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
int x = Math.abs(start - end);
int x = end - start;

int calculatedArea = x * y;
area = Math.max(area, calculatedArea);

// [중요] 포인터 이동 로직
if (height[start] <= height[end])
start++;
else
end--;
}
return area;
}
}
44 changes: 44 additions & 0 deletions design-add-and-search-words-data-structure/jinvicky.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
class WordDictionary {

private static class Node {
Node[] next = new Node[26];
boolean isEnd;
Copy link
Contributor

Choose a reason for hiding this comment

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

isEmpty는 함수이므로 저는 endended처럼 표현합니다.

}

private final Node root = new Node();

public WordDictionary() {}

public void addWord(String word) {
Node cur = root;
for (int k = 0; k < word.length(); k++) {
char ch = word.charAt(k);
int i = ch - 'a';
if (cur.next[i] == null) cur.next[i] = new Node();
cur = cur.next[i];
}
cur.isEnd = true;
}

public boolean search(String word) {
return dfs(word, 0, root);
}

private boolean dfs(String word, int idx, Node node) {
if (node == null) return false;
if (idx == word.length()) return node.isEnd;

char ch = word.charAt(idx);
if (ch == '.') {
// 모든 가능 문자로 한 글자 매칭
for (int c = 0; c < 26; c++) {
if (node.next[c] != null && dfs(word, idx + 1, node.next[c])) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (node.next[c] != null && dfs(word, idx + 1, node.next[c])) {
if (dfs(word, idx + 1, node.next[c])) {

return true;
}
}
return false;
} else {
return dfs(word, idx + 1, node.next[ch - 'a']);
}
}
}
39 changes: 39 additions & 0 deletions house-robber-ii/jinvicky.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
class Solution {
/**
* 기존 house-robber 1 문제에서 로직을 가져오되, 접근법을 달리하는 문제
* 처음생각했던 접근법은 그냥 dp값에서 첫번째 또는 마지막 요소를 빼서 나온 최댓값 아닌가? 했지만 범위에 따라 dp가 달라지므로 오답
*/
public int rob(int[] nums) {
if (nums.length == 1) return nums[0];
else if (nums.length == 2)
return Math.max(nums[0], nums[1]);
else if (nums.length == 3) {
return Math.max(Math.max(nums[0], nums[1]), nums[2]);
}
Comment on lines +8 to +12
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
else if (nums.length == 2)
return Math.max(nums[0], nums[1]);
else if (nums.length == 3) {
return Math.max(Math.max(nums[0], nums[1]), nums[2]);
}


int n = nums.length;
// 첫째 요소만 포함한 dp (마지막 요소 포함 X)
int[] firstDp = new int[n - 1];
firstDp[0] = nums[0];
for (int i = 1; i < n - 1; i++) {
int prev2AndNowRob = (i - 2 < 0 ? 0 : firstDp[i - 2]) + nums[i];
Comment on lines +15 to +19
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// 첫째 요소만 포함한 dp (마지막 요소 포함 X)
int[] firstDp = new int[n - 1];
firstDp[0] = nums[0];
for (int i = 1; i < n - 1; i++) {
int prev2AndNowRob = (i - 2 < 0 ? 0 : firstDp[i - 2]) + nums[i];
// 첫째 요소도 포함할 수 있는 dp
int[] firstDp = new int[n];
firstDp[0] = nums[0];
firstDp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < n; i++) {
int prev2AndNowRob = firstDp[i - 2] + nums[i];

int prev1Rob = firstDp[i - 1];

firstDp[i] = Math.max(prev2AndNowRob, prev1Rob);
}
// System.out.println(firstDp[n-2]); // ok

// 마지막 요소만 포함한 dp (첫번째 요소 포함 X)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// 마지막 요소만 포함한 dp (첫번째 요소 포함 X)
// 첫째 요소를 포함하지 않는 dp

int[] lastDp = new int[n];
lastDp[1] = nums[1];
for (int i = 2; i < n; i++) {
int prev2AndNowRob = (i - 2 < 1 ? 0 : lastDp[i - 2]) + nums[i];
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
int prev2AndNowRob = (i - 2 < 1 ? 0 : lastDp[i - 2]) + nums[i];
int prev2AndNowRob = lastDp[i - 2] + nums[i];

int prev1Rob = lastDp[i - 1];

lastDp[i] = Math.max(prev2AndNowRob, prev1Rob);
}
// System.out.println(lastDp[n-1]); // ok

return Math.max(firstDp[n - 2], lastDp[n - 1]);
}
}
27 changes: 22 additions & 5 deletions longest-consecutive-sequence/jinvicky.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import java.util.HashSet;
import java.util.Set;

// 연속적인 숫자의 길이를 구하는 것이기 때문에 이전, 다음 수가 집합의 일부인지를 파악해야 한다.
// map, set 자료구조를 사용하면 조회 성능을 O(1)로 높일 수 있다.
// 어려웠던 점은 연속적인 숫자의 start가 되냐 여부 조건을 떠올리는 것이었다. while문이 약해서 length++하는 로직이 힘들었다.
// 문제의 조건은 배열 내에서의 연속적인 숫자의 길이이기 때문에 while을 사용해도 성능 이슈 걱정할 필요가 없었다.
/**
* 왜 set을 썼을까? 내가 원하는 "특정 조건"을 제시했을 때 그 숫자를 O(1)으로 조회할 수 있기 때문이다.
* set이 줄 것을 알기에 나는 조건을 설계하는 데만 집중한다.
* 1. 내가 포함된 연속된 시퀀스가 있는가? -> set.contains(n-1)
* 2. 내가 새로운 시퀀스의 start인가? -> !set.contains(n-1)
* <p>
* 여기서 가장 긴 길이를 구한다 == Math.max(기존 최대길이, 현재 계산한 최대길이) -> 자동으로 Math.max()가 떠오른다.
* 현재 최대길이는 본인을 포함한 1부터 시작한다.
* [길이를 계산할 때 항상 해야 할까???] -> 아니다!
* 왜? 이미 내가 포함된 연속된 시퀀스는 maxLength를 비교하는 과정을 거쳤는데 굳이 또?
* <p>
* 배움: 일단 계산을 떠올린다. -> 그리고 그 계산을 언제(if) 수행할 것인지 조건을 설정한다. (항상 해도 되는가? 중복되지는 않는가?)
* <p>
* [성능에 대한 잘못된 생각]
* O(n)이라면 꼭 for문 1번으로 해결해야 한다는 잘못된 생각을 갖고 있었다.
* 첫 번째 루프가 O(n), 두 번째 루프가 O(n)이고, 두 개를 합치면 O(n + n)입니다.
Comment on lines +12 to +19
Copy link
Contributor

Choose a reason for hiding this comment

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

좋은 관찰이시네요. DFS나 BFS 코드도 그렇고 Flood Fill도 그렇죠.

* 시간 복잡도 계산에서 상수 계수는 무시되므로 결국 O(n)으로 표기한다.
* <p>
* [후기]
* 처음에는 어 기존이랑... 지금이랑 별도 배열로 체크? 그런데 배열의 개수가 어디까지 늘어나지...?
* 항상 기억할 점은 최대 길이, 최소 길이와 같은 문제는 결과가 중요하지 어느 숫자로 이루어져있는지 알 필요가 없다.
Copy link
Contributor

@yhkee0404 yhkee0404 Aug 28, 2025

Choose a reason for hiding this comment

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

역시 다양한 접근을 위해 좋은 관찰이십니다. 결정 문제는 아니지만 비슷한 환원 같네요.

*/
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> set = new HashSet<>();
Expand All @@ -26,7 +44,6 @@ public int longestConsecutive(int[] nums) {
maxLength = Math.max(length, maxLength);
}
}

return maxLength;
}
}
63 changes: 63 additions & 0 deletions longest-increasing-subsequence/jinvicky.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import java.util.ArrayList;
import java.util.List;

/**
* 가장 먼저 들었던 의문은 왜 dp가 2차원 배열이 아닐까? 였지만,
* dp를 2차원 자료구조로 선언하는 경우는 경로 저장이나 상태 전이가 2개 이상의 조건에 따라 나눠질 때인데
* 이 문제는 1차원으로 해결이 가능한 문제입니다. 가장 중요한 건 “마지막 원소 위치 i”라는 하나의 상태입니다.
*
* LIS = "주어진 수열에서 순서를 지키면서 고를 수 있는 원소 중, 값이 점점 커지도록 선택했을 때 만들 수 있는 가장 긴 부분 수열."
* brute force로 이중 for문을 사용하는 것은 비효율적입니다. 그래서 for문 + BS를 곁들였습니다.
Copy link
Contributor

Choose a reason for hiding this comment

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

O(2^n)인 Brute-Force도 있으므로, O(n^2)인 이중 for문도 DP는 맞다고 생각합니다.

*
* Arrays.binarySearch()는 정렬된 배열에서만 동작하므로 이 문제의 테스트 케이스에 맞지 않습니다.
Copy link
Contributor

Choose a reason for hiding this comment

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

후술하신 대로 원본 nums의 이분 탐색은 아니라는 말씀 같기는 합니다.
그래도 Stack의 길이를 별도의 변수 top으로 관리하면 Arrays.binarySearch(tails, 0, top, x)이 가능합니다.

*
* 신규 메서드: Collections.binarySearch()
* List 안의 요소들에 대해서 이분 탐색을 해서 해당 index를 반환합니다. (없을 경우 -1)
* 단, list 안의 요소들은 정렬되어 있어야 합니다 (이분 탐색의 기본 조건처럼)
* 신규 메서드 혹은 직접적으로 binarySearch() 메서드를 구현해도 됩니다. 다만 원본 nums를 이분 탐색하는 것이 아니며
* 직관적으로 개념을 이해하기 위해서 간편한 메서드 방식인 Collections.binarySearch()를 사용했습니다.
*
*
* [테스트 케이스 과정 출력]
* 0
* [10]
* 0
* [9]
* 0
* [2]
* 1
* [2, 5]
* 1
* [2, 3]
* 2
* [2, 3, 7]
* 3
* [2, 3, 7, 101]
* 3
* [2, 3, 7, 18]
*/
import java.util.*;

class Solution {
public int lengthOfLIS(int[] nums) {
List<Integer> tails = new ArrayList<>(); // 길이 k인 증가 수열의 "꼬리 최솟값"들

for (int x : nums) {
int idx = Collections.binarySearch(tails, x);
// 해당 x 숫자를 찾지 못하면 -1을 반환하면 이는 list에서 범위에 벗어나기 때문에
// 음수면 삽입 위치로 변환해서 범위 예외를 처리해야 합니다.
if (idx < 0) idx = -(idx + 1);

// 중요 연산은 더하기와 교체입니다.
// 가장 크다는 것: 주어진 인덱스와 꼬리 리스트의 길이가 같다. -> 새 subsequence가 생깁니다.
// 그렇지 않다는 것은 최댓값보다 더 작은 값이 있다는 것, 그 자리를 주어진 x로 교체한다.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// 그렇지 않다는 것은 최댓값보다 더 작은 값이 있다는 것, 그 자리를 주어진 x로 교체한다.
// 그렇지 않다는 것은 최댓값보다 더 작거나 같은 값이 있다는 것, 그 자리를 주어진 x로 교체한다.

// 결론: 가장 크면 add, 그 외에는 인덱스 자리를 x로 교체
if (idx == tails.size()) {
tails.add(x); // 가장 크면 뒤에 추가 → 길이 +1
} else {
tails.set(idx, x); // 아니면 그 자리의 꼬리를 더 작은 x로 교체
}
}
return tails.size(); // 꼬리 리스트 길이 = LIS 길이
}
}
24 changes: 24 additions & 0 deletions missing-number/jinvicky.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Solution {
/**
* 처음에는 set 자료구조를 동원해서 꼭 빠진 숫자를 찾겠다고 다짐했으나,
* 생각해보니 단순히 범위가 0부터 nums.length까지의 연속된 시퀀스라면
* 그냥 0부터 n까지 더했을 때의 원래 예상값에서 현재 nums의 합계를 빼면 되는 것이다.
*
* 최댓값, 최솟값을 구할때와 비슷하게 굳이 내용 안을 다 찾으려고 형식 자료구조에 얽매이지 않아도 된다.
Copy link
Contributor

Choose a reason for hiding this comment

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

하지만 결국 다 탐색하는 게 맞으니 그 경우와는 덜 비슷하다는 생각이 들어요. 반대로 Set 자료구조의 add, remove 연산을 숫자의 덧셈, 뺄셈으로 구현한 것에 불과할지도 모릅니다. 한편 XOR 연산도 가능합니다.

*/
public int missingNumber(int[] nums) {
int expected = 0; // 0부터 n까지 더한 숫자의 합계
int input = 0; //nums가 준 숫자들의 합계

for (int n : nums) {
input += n;
}

for (int i = 0; i <= nums.length; i++) {
expected += i;
}
// System.out.println(expected + " and " + input);

return expected - input;
}
}
38 changes: 38 additions & 0 deletions spiral-matrix/jinvicky.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import java.util.*;

/**
* dir[][]의 방향을 나선으로 맞추는 것이 가장 중요. 단순 dfs, bfs의 4방향이 아님.
*/
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> ans = new ArrayList<>();
int m = matrix.length;
if (m == 0) return ans;
int n = matrix[0].length;

boolean[][] visited = new boolean[m][n];

// 한 배열에 (row, col) 방향쌍을 보관: → ↓ ← ↑
int[][] dir = {{0,1}, {1,0}, {0,-1}, {-1,0}};
int d = 0; // 현재 방향 인덱스
int i = 0, j = 0; // 현재 위치

for (int k = 0; k < m * n; k++) {
ans.add(matrix[i][j]);
visited[i][j] = true;

int ni = i + dir[d][0];
int nj = j + dir[d][1];

// 경계 밖이거나 이미 방문했다면 방향 전환
if (ni < 0 || ni >= m || nj < 0 || nj >= n || visited[ni][nj]) {
d = (d + 1) % 4; // 0→1→2→3→0
ni = i + dir[d][0];
nj = j + dir[d][1];
}

i = ni; j = nj;
}
return ans;
}
}
31 changes: 15 additions & 16 deletions top-k-frequent-elements/jinvicky.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.*;

class Solution {
public int[] topKFrequent(int[] nums, int k) {
// [풀이]
// 1. <숫자: 빈도수>를 저장하는 HashMap과 [빈도수, 숫자]를 저장하는 PriorityQueue를 선언한다.
// 2. HashMap에 숫자별로 빈도수를 함께 저장해서 해시테이블을 만든다.
// [우선순위 큐에 사용된 자료구조]
// 1. 별도 클래스를 선언
// 2. 요구사항 자료형 배열을 선언한다.
// 처음에는 별도 클래스를 선언했다가 값이 2개이며 알고리즘 로직 자체가 어려워서 int[] 구조로 풀이했다.
// (주로 알고리즘이 어려우면 가독성이 나쁘더라도 자료구조를 단순화하는 습관이 있다)
// [어려웠던 점]
// 1. 우선순위 큐는 매번 요소가 추가될 때마다 내부 정렬을 수행하기 때문에 연산을 수행하면서 k개를 유지해야 한다.
// 또한 기존 [빈도수, 숫자]를 버려야만 올바른 답을 도출할 수 있었다.
// 2. [숫자, 빈도수]로 저장하는 것만 생각했더니 내부 정렬을 어떻게 하지 못해서 굉장히 고민했다. 정답은 반대였다.
Comment on lines -17 to -18
Copy link
Contributor

Choose a reason for hiding this comment

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

Database의 Secondary Index 또는 Inverted Index와 비슷한 원리 같더라고요.


/**
* for문이 총 3번 필요하다. (1.빈도_초기화, 2.큐에 저장, 3.결과배열에 할당
* 빈도_초기화 -> k:v로 쉽게 저장하는 자료구조
* 큐에 저장 -> 최소 힙으로 정렬되는 우선순위 큐 선언 -> 큐 같은 자료구조에서 k:v를 하고 싶다면 new int[2]가 가장 쉬움
* 결과배열에 할당 -> k개만큼 answer[]에 저장
*
* 문제는 상위 k개를 유지해야 하고 그를 위해서는 k개를 넘었을 때 빈도수가 낮은 순으로 flush하는 로직이 필요하다는 것.
* 큐는 선입선출 -> 빈도수가 낮을 수록 위로 정렬되게 해야 flush했을 때 빈도수가 낮은 순서대로 사라진다.
*
* 결과배열에 할당할 때는 그냥 앞에서부터 하면 된다.
* 이미 k개를 만족했고, 빈도수가 낮 -> 높 순서대로 그대로 쌓으면 된다.
*
* 첫번째부터 꺼내서 결과배열에 할당할 거면 [빈도수, 숫자]로 큐에 저장하는 게 맞다
*/
int[] answer = new int[k];

Map<Integer, Integer> map = new HashMap<>();
Expand Down
36 changes: 36 additions & 0 deletions valid-parentheses/jinvicky.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import java.util.Stack;

class Solution {
/**
* 주어진 문자열 s의 괄호가 제대로 닫혀있는지 여부를 판단하는 문제입니다.
* 나올 수 있는 괄호의 종류는 3개이며 짝맞추기에 유리한 자료구조인 스택을 사용합니다.
* {, [, ( 같은 왼쪽 괄호는 무조건 stack에 추가됩니다.
* }, ], ) 같은 오른쪽 괄호를 만나면 매칭되는 왼쪽 괄호를 만날 때까지 pop()을 통해 stack에서 괄호들을 꺼내야 합니다.
*/
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();

for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else {
/**
* [중요] 문자열이 오른쪽 괄호로만 구성되는 경우도 있으므로 for문이 반복되는데 스택이 비었다면
* 그 즉시 false를 반환하고 break해야만 stack Exception을 막을 수 있습니다.
* 문제 케이스: "]"
*/
if (stack.isEmpty()) {
return false;
}
if (stack.peek() == '(' && c == ')') {
stack.pop();
} else if (stack.peek() == '[' && c == ']') {
stack.pop();
} else if (stack.peek() == '{' && c == '}') {
stack.pop();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

조기 종료도 가능합니다:

Suggested change
}
} else {
return false;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

오 조기 종료 생각을 못했네요! 감사합니다

Comment on lines +25 to +31
Copy link
Contributor

@yhkee0404 yhkee0404 Aug 25, 2025

Choose a reason for hiding this comment

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

Suggested change
if (stack.peek() == '(' && c == ')') {
stack.pop();
} else if (stack.peek() == '[' && c == ']') {
stack.pop();
} else if (stack.peek() == '{' && c == '}') {
stack.pop();
}
if (stack.peek() == '(' && c == ')'
|| stack.peek() == '[' && c == ']'
|| stack.peek() == '{' && c == '}') {
stack.pop();
}

}
}
return stack.isEmpty(); // 스택이 비었다면 괄호는 짝이 맞는다는 의미입니다.
}
}