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
30 changes: 30 additions & 0 deletions best-time-to-buy-and-sell-stock/jinvicky.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* 한글주석만 보고 코드를 작성해본다.
* 조건문 안의 조건만 코드를 비우거나, 조건문만 남기고 안의 액션코드를 비우고 다시 작성해본다.
*
*/
class Solution {
public int maxProfit(int[] prices) {
/**
max라는 메모이제이션 변수를 int로 선언한다.
dp로 모든 경우의 수를 고려할 것이다.
주식의 최소가격을 담은 변수를 int로 선언한다. 맨 처음에 prices[0] 값이 되면 좋겠다.
*/
int max = 0; // dp memoization 변수
int min = prices[0]; // 주식 배열의 최소값
for (int i = 1; i < prices.length; i++) {
/**
현재 주식은 팔 때의 주식 가격을 나타낸다.
max값은 (기존 최대이익, 최소주식을 현재 주식값에 팔았을 때의 이익) 중 더 큰 값으로 업데이트된다.
현재 주식값이 기존 최소 주식값보다 작다면 현재 주식값으로 최솟값을 업데이트한다.
*/
int currentMax = prices[i] - min;
max = Math.max(max, currentMax);
if (prices[i] < min) {
min = prices[i];
}
}

return max;
}
}
28 changes: 20 additions & 8 deletions coin-change/jinvicky.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import java.util.Arrays;


// 왜 greedy가 아닐까? amount가 7이라고 가정했을 때 greedy라면 [1,3,4,5]에서 5를 우선시해서 5+1+1 총 3개를 사용할 것이지만,
// 실제 정답은 3+4 로 총 2개가 된다. 그렇기에 단순히 욕심쟁이로 최댓값, 최솟값으로 접근하는 방식은 적합하지 않다.
// greedy가 아니라면 보통 DP로 풀이한다.
// DP의 기초인 1. Climbing Stairs 2. Best Time to Buy and Sell Stock 3. House Robber를 선행으로 풀고 접근했다.
// DP의 기본은 i-1, i-2식으로 기존 값을 재사용하는 메모이제이션인데 나는 bottom-up으로 모든 dp를 풀이하려고 노력한다. (일관성)
// DP는 값을 비교하기 위해서 Math.min(), Math.max()를 많이 사용하기에 초기값을 Integer.MAX_VALUE, Integer.MIN_VALUE로 염두했다.

class Solution {
public int coinChange(int[] coins, int amount) {
int max=amount+1;
int [] dp=new int[amount+1];
Arrays.fill(dp,max);
dp[0]=0;
for(int coin:coins){
for(int j=coin;j<=amount;j++){ // coin부터 시작해서 일반 루프보다 성능 향상
dp[j]=Math.min(dp[j],dp[j-coin]+1);
int max = amount + 1; // 값 미표현이라면 Integer.MAX_VALUE가 더 직관적이지 않나? -> testcase에서 음수값 나와서 실패함
int[] dp = new int[amount + 1]; // 목표인 amount를 위한 공간 확보를 위해서 amount+1로 배열 사이즈를 측정
Arrays.fill(dp, max); // 최소 개수를 구하는 것이기 때문에 max값으로 채웠다.
dp[0] = 0; // 첫 요소만 max가 아닌 0으로 설정한다. ->
// 그냥 0부터 amount까지 이중 for문 하는 것보다
// j문에서 바깥 i의 코인값부터 인덱스로 시작하는 것이 같은 결과, 높은 성능.
for (int coin : coins) {
for (int j = coin; j <= amount; j++) {
// dp[j - coin]: coin 동전을 하나 사용했을 때, 남은 금액 j - coin을 만드는 최소 동전 개수
// 코인값을 인덱스로서 계산해야 한다는 것을 몰랐음 j-coin에서 한참을 헤맨....
dp[j] = Math.min(dp[j], dp[j - coin] + 1);
}
}
return dp[amount]>amount ? -1:dp[amount];
return dp[amount] > amount ? -1 : dp[amount];
}
}
37 changes: 37 additions & 0 deletions group-anagrams/jinvicky.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import java.util.*;
import java.util.stream.Collectors;

class Solution {
/**
* 41ms를 가진 낮은 성능의 첫번째 정답 코드
*/
public List<List<String>> groupAnagrams(String[] strs) {
// 길이가 1이면 자체를 List로 감싸서 반환한다.
if (strs.length == 1) {
return List.of(List.of(strs[0]));
}

List<List<String>> groupList = new ArrayList<>();
/**
* bf로 그룹핑을 할 수 있나?
* strs를 for문으로 돌면서 "정렬한" 단어가 map에 있는 지 확인하고 존재할 경우 key로 조회한 list에 추가한다.
* 마지막으로 map을 순회하면서 list들을 groupList.add(list); 한다.
*/
Map<String, List<String>> map = new HashMap<>();
for (String s : strs) {
String sortedS = Arrays.stream(s.split(""))
.sorted()
.collect(Collectors.joining());
List<String> v = map.get(sortedS);
if (v == null) {
map.put(sortedS, new ArrayList<>());
}
map.get(sortedS).add(s);
}

for (List<String> list : map.values()) {
groupList.add(list);
}
return groupList;
Copy link
Contributor

Choose a reason for hiding this comment

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

groupList 변수 필요 없이 return new ArrayList<>(map.values()) 이런식으로도 가능하지는 않을까요?

그리고 groupList를 사용하는 곳 근처에 선언부가 있으면 좋을 것 같습니다. 선언하는 라인과 사용하는 라인 사이에 for loop이 있다보니 거기서 groupList를 사용하는 것처럼 보여서 다시 확인하게 되어서요.

}
}
40 changes: 40 additions & 0 deletions implement-trie-prefix-tree/jinvicky.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import java.util.ArrayList;
import java.util.List;

/**
* brute-force로 먼저 접근한 423ms의 최악의 성능 코드.
*/
class Trie {

private List<String> list;

public Trie() {
this.list = new ArrayList<>();
}

public void insert(String word) {
this.list.add(word);
}

public boolean search(String word) {
for(String s : list) {
if(s.equals(word)) return true;
}
return false;
}

public boolean startsWith(String prefix) {
for(String s : list) {
if(s.startsWith(prefix)) return true;
}
return false;
}
}

/**
* Your Trie object will be instantiated and called as such:
* Trie obj = new Trie();
* obj.insert(word);
* boolean param_2 = obj.search(word);
* boolean param_3 = obj.startsWith(prefix);
*/
69 changes: 69 additions & 0 deletions word-break/jinvicky.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import java.util.*;

class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
int n = s.length();
boolean[] dp = new boolean[n + 1]; // 빈 문자열을 대비해서 +1 길이로 설정
dp[0] = true; // 빈 문자열은 항상 가능

for (int i = 0; i < n; i++) {
if (!dp[i]) continue; // 여기까지 못 오면 확장 불가
for (String w : wordDict) {
int j = i + w.length();
if (j <= n && s.startsWith(w, i)) {
dp[j] = true;
}
}
}
return dp[n];
}

public boolean wordBreak2(String s, List<String> wordDict) {
int n = s.length();
// List를 HashSet 객체 생성시 인자로 넣어 초기화가 가능합니다.
// set은 List에서의 단어 탐색을 O(1) 성능으로 최적화하기 위해서 필요합니다.
Set<String> wordSet = new HashSet<>(wordDict);

// 빈 문자열일 경우를 대비해서 +1을 길이로 설정합니다.
boolean[] dp = new boolean[n + 1];
// 빈 문자열을 항상 true이므로 0번째 dp[]에 true를 설정합니다.
dp[0] = true;

// 앞서 빈문자열을 셋팅했으니 i는 1부터 n까지 반복합니다.
// j는 0부터 i보다 작을때까지 반복합니다.
/**
* j=0: s.substring(0, 1) = "l" | dp[0]=true | "l" in dict=false
* -----
* j=0: s.substring(0, 2) = "le" | dp[0]=true | "le" in dict=false
* j=1: s.substring(1, 2) = "e" | dp[1]=false | "e" in dict=false
* -----
* j=0: s.substring(0, 3) = "lee" | dp[0]=true | "lee" in dict=false
* j=1: s.substring(1, 3) = "ee" | dp[1]=false | "ee" in dict=false
* j=2: s.substring(2, 3) = "e" | dp[2]=false | "e" in dict=false
* -----
* j=0: s.substring(0, 4) = "leet" | dp[0]=true | "leet" in dict=true → dp[4] = true!
* -----
* j=0: s.substring(0, 5) = "leetc" | dp[0]=true | "leetc" in dict=false
* j=1: s.substring(1, 5) = "eetc" | dp[1]=false | "eetc" in dict=false
* j=2: s.substring(2, 5) = "etc" | dp[2]=false | "etc" in dict=false
* j=3: s.substring(3, 5) = "tc" | dp[3]=false | "tc" in dict=false
* j=4: s.substring(4, 5) = "c" | dp[4]=true | "c" in dict=false
* -----
* j=0: s.substring(0, 8) = "leetcode" | dp[0]=true | "leetcode" in dict=false
* j=1: s.substring(1, 8) = "eetcode" | dp[1]=false | "eetcode" in dict=false
* j=2: s.substring(2, 8) = "etcode" | dp[2]=false | "etcode" in dict=false
* j=3: s.substring(3, 8) = "tcode" | dp[3]=false | "tcode" in dict=false
* j=4: s.substring(4, 8) = "code" | dp[4]=true | "code" in dict=true → dp[8] = true!
*/
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
// 예시를 보면 (start, end)에서 start가 증가할 동안 end는 고정됩니다. 따라서 start=j, end=i가 되어야 합니다.
if (dp[j] && wordSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[n];
}
}
52 changes: 37 additions & 15 deletions word-search/jinvicky.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@ class Solution {
public boolean exist(char[][] board, String word) {
int m = board.length;
int n = board[0].length;
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(dfs(board, i, j, word, 0)) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (dfs(board, i, j, word, 0)) {
return true;
}
}
}
return false;
}

boolean dfs(char[][] board,int i,int j,String word,int index){
if(index == word.length()) return true;
if(i<0 || j<0 || i>=board.length || j>=board[0].length) return false; // 범위를 벗어난 경우
if(board[i][j] != word.charAt(index)) return false; // 일치 조건을 불만족하는 경우
boolean dfs(char[][] board, int i, int j, String word, int index) {
if (index == word.length()) return true;
if (i < 0 || j < 0 || i >= board.length || j >= board[0].length) return false; // 범위를 벗어난 경우
if (board[i][j] != word.charAt(index)) return false; // 일치 조건을 불만족하는 경우

char temp = board[i][j];
board[i][j] = '#';
boolean found = dfs(board, i+1, j, word, index+1)
|| dfs(board, i-1, j, word, index+1)
|| dfs(board, i, j+1, word, index+1)
|| dfs(board, i, j-1, word, index+1);
boolean found = dfs(board, i + 1, j, word, index + 1)
|| dfs(board, i - 1, j, word, index + 1)
|| dfs(board, i, j + 1, word, index + 1)
|| dfs(board, i, j - 1, word, index + 1);

board[i][j] = temp;
return found;
Expand All @@ -48,22 +48,44 @@ public boolean exist2(char[][] board, String word) {
return false;
}

// 백트래킹 인자 국룰 (인자로 받는다는 건 다음에 필요함 + 다음 재귀때 업데이트된 값이 필요해서)
// 재귀여야 하기 때문에 2차원 input과 방향을 위한 i, j를 받도록 한다.
// 매번 조건 일치를 확인하기 위한 target 값이 인자로 포함된다 (문제마다 다름)

// private boolean backtrack (원본 2차원 배열, 타겟, 2차원 방문 배열, i 방향 인덱스, j방향 인덱스, 조건부 target) {
// break1. 조건을 만족하면 값을 반환
// break2. i와 j의 범위가 0보다 작거나 원본 배열의 길이보다 크거나 같으면 탈락
// break3. 방문배열[i][j]가 true거나 (이미 방문했음) 또는 문제의 만족 조건이 아닐 경우 탈락
// 보통 break2, break3을 한번의 if문으로 작성하지만 난 그마저도 어려워서 한번 더 분리했다.
//
// action1. 방문배열[i][j] 를 true로 설정한다.
// action2. i와 j를 위한 2차원 방향배열 템플릿을 선언한다.
// action3. 방향은 무조건 동서남북 4방향 고정이다. 4만큼 반복되는 for문을 실행한다.
// action3-1. dir[0], dir[1]을 i와 j에게 더해서 nextI, nextJ로 만들어 backtrack()에 전달한다. (이때 조건이 맞으면 return으로 break)
// action4. return을 못한 경우 방문배열[i][j]를 false로 재설정한다.
// action5. false 등을 리턴한다.
// }


private boolean backtrack(char[][] board, String word, boolean[][] visited, int i, int j, int index) {
if (index == word.length()) {
return true;
}

// dfs를 풀 때는 배열 범위를 벗어나는 경우 break를 꼭 기억하기
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || visited[i][j]
|| board[i][j] != word.charAt(index)) {
// dfs를 풀 때는 배열 범위를 벗어나는 경우는 모든 문제 공통이니 그냥 i,j기준으로 0와 length 생각하며 암기
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length) {
return false;
}

if (visited[i][j] || board[i][j] != word.charAt(index)) {
return false;
}

visited[i][j] = true;

// if문에서 작성하는 것이 눈에 안 익어서
// direction 템플릿을 만들어서 for문으로 해결하는 암기법으로 변환
int[][] directions = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } }; // 그냥 암기
int[][] directions = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; // 그냥 암기

for (int[] dir : directions) {
// i가 y, j가 x인데 사실 1, -1, 0만 잘 설정하면 상관없음. 목적은 1, -1 1번씩 그리고 나머지는 0으로 채우는 것
Expand Down