diff --git a/container-with-most-water/jinvicky.java b/container-with-most-water/jinvicky.java
new file mode 100644
index 000000000..b9c91fd2a
--- /dev/null
+++ b/container-with-most-water/jinvicky.java
@@ -0,0 +1,26 @@
+/**
+ * 구하려는 것은 최대 영역값이기 때문에 x,y축을 계산한 현재 영역과 기존 영역을 비교해 max값을 반환하면 됩니다.
+ * 포인터를 어떻게 좁힐까 생각할 수 있는데 쉽게 말해서 start와 end 중 더 작은 쪽을 앞 또는 뒤로 이동하면 됩니다.
+ * 영역은 두 막대가 모두 충족가능한 길이가 되어야 하므로 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); // end - start도 가능
+ int calculatedArea = x * y;
+ area = Math.max(area, calculatedArea);
+
+ // [중요] 포인터 이동 로직
+ if (height[start] <= height[end])
+ start++;
+ else
+ end--;
+ }
+ return area;
+ }
+}
diff --git a/design-add-and-search-words-data-structure/jinvicky.java b/design-add-and-search-words-data-structure/jinvicky.java
new file mode 100644
index 000000000..c6d73aa77
--- /dev/null
+++ b/design-add-and-search-words-data-structure/jinvicky.java
@@ -0,0 +1,44 @@
+class WordDictionary {
+
+ private static class Node {
+ Node[] next = new Node[26];
+ boolean isEnd;
+ }
+
+ 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])) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return dfs(word, idx + 1, node.next[ch - 'a']);
+ }
+ }
+}
diff --git a/house-robber-ii/jinvicky.java b/house-robber-ii/jinvicky.java
new file mode 100644
index 000000000..89235f421
--- /dev/null
+++ b/house-robber-ii/jinvicky.java
@@ -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]);
+ }
+
+ 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];
+ int prev1Rob = firstDp[i - 1];
+
+ firstDp[i] = Math.max(prev2AndNowRob, prev1Rob);
+ }
+ // System.out.println(firstDp[n-2]); // ok
+
+ // 마지막 요소만 포함한 dp (첫번째 요소 포함 X)
+ 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];
+ 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]);
+ }
+}
diff --git a/longest-consecutive-sequence/jinvicky.java b/longest-consecutive-sequence/jinvicky.java
index 01e9cf62a..fc191129b 100644
--- a/longest-consecutive-sequence/jinvicky.java
+++ b/longest-consecutive-sequence/jinvicky.java
@@ -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)
+ *
+ * 여기서 가장 긴 길이를 구한다 == Math.max(기존 최대길이, 현재 계산한 최대길이) -> 자동으로 Math.max()가 떠오른다.
+ * 현재 최대길이는 본인을 포함한 1부터 시작한다.
+ * [길이를 계산할 때 항상 해야 할까???] -> 아니다!
+ * 왜? 이미 내가 포함된 연속된 시퀀스는 maxLength를 비교하는 과정을 거쳤는데 굳이 또?
+ *
+ * 배움: 일단 계산을 떠올린다. -> 그리고 그 계산을 언제(if) 수행할 것인지 조건을 설정한다. (항상 해도 되는가? 중복되지는 않는가?)
+ *
+ * [성능에 대한 잘못된 생각]
+ * O(n)이라면 꼭 for문 1번으로 해결해야 한다는 잘못된 생각을 갖고 있었다.
+ * 첫 번째 루프가 O(n), 두 번째 루프가 O(n)이고, 두 개를 합치면 O(n + n)입니다.
+ * 시간 복잡도 계산에서 상수 계수는 무시되므로 결국 O(n)으로 표기한다.
+ *
+ * [후기]
+ * 처음에는 어 기존이랑... 지금이랑 별도 배열로 체크? 그런데 배열의 개수가 어디까지 늘어나지...?
+ * 항상 기억할 점은 최대 길이, 최소 길이와 같은 문제는 결과가 중요하지 어느 숫자로 이루어져있는지 알 필요가 없다.
+ */
class Solution {
public int longestConsecutive(int[] nums) {
Set set = new HashSet<>();
@@ -26,7 +44,6 @@ public int longestConsecutive(int[] nums) {
maxLength = Math.max(length, maxLength);
}
}
-
return maxLength;
}
}
diff --git a/longest-increasing-subsequence/jinvicky.java b/longest-increasing-subsequence/jinvicky.java
new file mode 100644
index 000000000..9949450b3
--- /dev/null
+++ b/longest-increasing-subsequence/jinvicky.java
@@ -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를 곁들였습니다.
+ *
+ * Arrays.binarySearch()는 정렬된 배열에서만 동작하므로 이 문제의 테스트 케이스에 맞지 않습니다.
+ *
+ * 신규 메서드: 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 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로 교체한다.
+ // 결론: 가장 크면 add, 그 외에는 인덱스 자리를 x로 교체
+ if (idx == tails.size()) {
+ tails.add(x); // 가장 크면 뒤에 추가 → 길이 +1
+ } else {
+ tails.set(idx, x); // 아니면 그 자리의 꼬리를 더 작은 x로 교체
+ }
+ }
+ return tails.size(); // 꼬리 리스트 길이 = LIS 길이
+ }
+}
diff --git a/maximum-product-subarray/jinvicky.java b/maximum-product-subarray/jinvicky.java
new file mode 100644
index 000000000..523554013
--- /dev/null
+++ b/maximum-product-subarray/jinvicky.java
@@ -0,0 +1,33 @@
+class Solution {
+ public int maxProduct(int[] nums) {
+ if (nums.length == 1)
+ return nums[0];
+ if (nums.length == 2) {
+ return Math.max(nums[0], Math.max(nums[0] * nums[1], nums[1]));
+ }
+
+ int len = nums.length;
+ int[] max = new int[len];
+ int[] min = new int[len];
+ int overall = 0;
+
+ max[0] = min[0] = overall = nums[0];
+
+ for (int i = 1; i < len; i++) {
+ // 후보 3을 준비
+ int justNum = nums[i];
+ // 계속 더한 값
+ int keep = justNum * max[i-1];
+ // 이전 최소에 음수 곱해서 리버스
+ int reverse = justNum * min[i-1];
+
+ // max와 min 배열을 업데이트
+ max[i] = Math.max(justNum, Math.max(keep, reverse));
+ min[i] = Math.min(justNum, Math.min(keep, reverse));
+
+ // overall을 업데이트, 누적 비교로 최대 전역 유지
+ overall = Math.max(overall, max[i]);
+ }
+ return overall;
+ }
+}
diff --git a/missing-number/jinvicky.java b/missing-number/jinvicky.java
new file mode 100644
index 000000000..6665b2516
--- /dev/null
+++ b/missing-number/jinvicky.java
@@ -0,0 +1,24 @@
+class Solution {
+ /**
+ * 처음에는 set 자료구조를 동원해서 꼭 빠진 숫자를 찾겠다고 다짐했으나,
+ * 생각해보니 단순히 범위가 0부터 nums.length까지의 연속된 시퀀스라면
+ * 그냥 0부터 n까지 더했을 때의 원래 예상값에서 현재 nums의 합계를 빼면 되는 것이다.
+ *
+ * 최댓값, 최솟값을 구할때와 비슷하게 굳이 내용 안을 다 찾으려고 형식 자료구조에 얽매이지 않아도 된다.
+ */
+ 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;
+ }
+}
diff --git a/spiral-matrix/jinvicky.java b/spiral-matrix/jinvicky.java
new file mode 100644
index 000000000..ec06bf5ec
--- /dev/null
+++ b/spiral-matrix/jinvicky.java
@@ -0,0 +1,38 @@
+import java.util.*;
+
+/**
+ * dir[][]의 방향을 나선으로 맞추는 것이 가장 중요. 단순 dfs, bfs의 4방향이 아님.
+ */
+class Solution {
+ public List spiralOrder(int[][] matrix) {
+ List 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;
+ }
+}
diff --git a/top-k-frequent-elements/jinvicky.java b/top-k-frequent-elements/jinvicky.java
index 005411266..3ae172bd1 100644
--- a/top-k-frequent-elements/jinvicky.java
+++ b/top-k-frequent-elements/jinvicky.java
@@ -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. [숫자, 빈도수]로 저장하는 것만 생각했더니 내부 정렬을 어떻게 하지 못해서 굉장히 고민했다. 정답은 반대였다.
-
+ /**
+ * 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 map = new HashMap<>();
diff --git a/unique-paths/jinvicky.java b/unique-paths/jinvicky.java
new file mode 100644
index 000000000..a7430fd9e
--- /dev/null
+++ b/unique-paths/jinvicky.java
@@ -0,0 +1,24 @@
+class Solution {
+ 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++) {
+ dp[i][0] = 1;
+ }
+
+ for (int j = 0; j < n; j++) {
+ dp[0][j] = 1;
+ }
+
+ for (int i = 1; i < m; i++) {
+ for (int j = 1; j < n; j++) {
+ dp[i][j] = dp[i-1][j] + dp[i][j-1];
+ }
+ }
+ return dp[m - 1][n - 1];
+ }
+}
diff --git a/valid-parentheses/jinvicky.java b/valid-parentheses/jinvicky.java
new file mode 100644
index 000000000..8301b5354
--- /dev/null
+++ b/valid-parentheses/jinvicky.java
@@ -0,0 +1,36 @@
+import java.util.Stack;
+
+class Solution {
+ /**
+ * 주어진 문자열 s의 괄호가 제대로 닫혀있는지 여부를 판단하는 문제입니다.
+ * 나올 수 있는 괄호의 종류는 3개이며 짝맞추기에 유리한 자료구조인 스택을 사용합니다.
+ * {, [, ( 같은 왼쪽 괄호는 무조건 stack에 추가됩니다.
+ * }, ], ) 같은 오른쪽 괄호를 만나면 매칭되는 왼쪽 괄호를 만날 때까지 pop()을 통해 stack에서 괄호들을 꺼내야 합니다.
+ */
+ public boolean isValid(String s) {
+ Stack 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();
+ }
+ }
+ }
+ return stack.isEmpty(); // 스택이 비었다면 괄호는 짝이 맞는다는 의미입니다.
+ }
+}