Skip to content

Commit f9cd537

Browse files
authored
Merge pull request #1836 from jinvicky/main
[jinvicky] WEEK05 solutions
2 parents 809841a + 1f95483 commit f9cd537

File tree

6 files changed

+233
-23
lines changed

6 files changed

+233
-23
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* 한글주석만 보고 코드를 작성해본다.
3+
* 조건문 안의 조건만 코드를 비우거나, 조건문만 남기고 안의 액션코드를 비우고 다시 작성해본다.
4+
*
5+
*/
6+
class Solution {
7+
public int maxProfit(int[] prices) {
8+
/**
9+
max라는 메모이제이션 변수를 int로 선언한다.
10+
dp로 모든 경우의 수를 고려할 것이다.
11+
주식의 최소가격을 담은 변수를 int로 선언한다. 맨 처음에 prices[0] 값이 되면 좋겠다.
12+
*/
13+
int max = 0; // dp memoization 변수
14+
int min = prices[0]; // 주식 배열의 최소값
15+
for (int i = 1; i < prices.length; i++) {
16+
/**
17+
현재 주식은 팔 때의 주식 가격을 나타낸다.
18+
max값은 (기존 최대이익, 최소주식을 현재 주식값에 팔았을 때의 이익) 중 더 큰 값으로 업데이트된다.
19+
현재 주식값이 기존 최소 주식값보다 작다면 현재 주식값으로 최솟값을 업데이트한다.
20+
*/
21+
int currentMax = prices[i] - min;
22+
max = Math.max(max, currentMax);
23+
if (prices[i] < min) {
24+
min = prices[i];
25+
}
26+
}
27+
28+
return max;
29+
}
30+
}

coin-change/jinvicky.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
11
import java.util.Arrays;
22

3+
4+
// 왜 greedy가 아닐까? amount가 7이라고 가정했을 때 greedy라면 [1,3,4,5]에서 5를 우선시해서 5+1+1 총 3개를 사용할 것이지만,
5+
// 실제 정답은 3+4 로 총 2개가 된다. 그렇기에 단순히 욕심쟁이로 최댓값, 최솟값으로 접근하는 방식은 적합하지 않다.
6+
// greedy가 아니라면 보통 DP로 풀이한다.
7+
// DP의 기초인 1. Climbing Stairs 2. Best Time to Buy and Sell Stock 3. House Robber를 선행으로 풀고 접근했다.
8+
// DP의 기본은 i-1, i-2식으로 기존 값을 재사용하는 메모이제이션인데 나는 bottom-up으로 모든 dp를 풀이하려고 노력한다. (일관성)
9+
// DP는 값을 비교하기 위해서 Math.min(), Math.max()를 많이 사용하기에 초기값을 Integer.MAX_VALUE, Integer.MIN_VALUE로 염두했다.
10+
311
class Solution {
412
public int coinChange(int[] coins, int amount) {
5-
int max=amount+1;
6-
int [] dp=new int[amount+1];
7-
Arrays.fill(dp,max);
8-
dp[0]=0;
9-
for(int coin:coins){
10-
for(int j=coin;j<=amount;j++){ // coin부터 시작해서 일반 루프보다 성능 향상
11-
dp[j]=Math.min(dp[j],dp[j-coin]+1);
13+
int max = amount + 1; // 값 미표현이라면 Integer.MAX_VALUE가 더 직관적이지 않나? -> testcase에서 음수값 나와서 실패함
14+
int[] dp = new int[amount + 1]; // 목표인 amount를 위한 공간 확보를 위해서 amount+1로 배열 사이즈를 측정
15+
Arrays.fill(dp, max); // 최소 개수를 구하는 것이기 때문에 max값으로 채웠다.
16+
dp[0] = 0; // 첫 요소만 max가 아닌 0으로 설정한다. ->
17+
// 그냥 0부터 amount까지 이중 for문 하는 것보다
18+
// j문에서 바깥 i의 코인값부터 인덱스로 시작하는 것이 같은 결과, 높은 성능.
19+
for (int coin : coins) {
20+
for (int j = coin; j <= amount; j++) {
21+
// dp[j - coin]: coin 동전을 하나 사용했을 때, 남은 금액 j - coin을 만드는 최소 동전 개수
22+
// 코인값을 인덱스로서 계산해야 한다는 것을 몰랐음 j-coin에서 한참을 헤맨....
23+
dp[j] = Math.min(dp[j], dp[j - coin] + 1);
1224
}
1325
}
14-
return dp[amount]>amount ? -1:dp[amount];
26+
return dp[amount] > amount ? -1 : dp[amount];
1527
}
1628
}

group-anagrams/jinvicky.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import java.util.*;
2+
import java.util.stream.Collectors;
3+
4+
class Solution {
5+
/**
6+
* 41ms를 가진 낮은 성능의 첫번째 정답 코드
7+
*/
8+
public List<List<String>> groupAnagrams(String[] strs) {
9+
// 길이가 1이면 자체를 List로 감싸서 반환한다.
10+
if (strs.length == 1) {
11+
return List.of(List.of(strs[0]));
12+
}
13+
14+
List<List<String>> groupList = new ArrayList<>();
15+
/**
16+
* bf로 그룹핑을 할 수 있나?
17+
* strs를 for문으로 돌면서 "정렬한" 단어가 map에 있는 지 확인하고 존재할 경우 key로 조회한 list에 추가한다.
18+
* 마지막으로 map을 순회하면서 list들을 groupList.add(list); 한다.
19+
*/
20+
Map<String, List<String>> map = new HashMap<>();
21+
for (String s : strs) {
22+
String sortedS = Arrays.stream(s.split(""))
23+
.sorted()
24+
.collect(Collectors.joining());
25+
List<String> v = map.get(sortedS);
26+
if (v == null) {
27+
map.put(sortedS, new ArrayList<>());
28+
}
29+
map.get(sortedS).add(s);
30+
}
31+
32+
for (List<String> list : map.values()) {
33+
groupList.add(list);
34+
}
35+
return groupList;
36+
}
37+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import java.util.ArrayList;
2+
import java.util.List;
3+
4+
/**
5+
* brute-force로 먼저 접근한 423ms의 최악의 성능 코드.
6+
*/
7+
class Trie {
8+
9+
private List<String> list;
10+
11+
public Trie() {
12+
this.list = new ArrayList<>();
13+
}
14+
15+
public void insert(String word) {
16+
this.list.add(word);
17+
}
18+
19+
public boolean search(String word) {
20+
for(String s : list) {
21+
if(s.equals(word)) return true;
22+
}
23+
return false;
24+
}
25+
26+
public boolean startsWith(String prefix) {
27+
for(String s : list) {
28+
if(s.startsWith(prefix)) return true;
29+
}
30+
return false;
31+
}
32+
}
33+
34+
/**
35+
* Your Trie object will be instantiated and called as such:
36+
* Trie obj = new Trie();
37+
* obj.insert(word);
38+
* boolean param_2 = obj.search(word);
39+
* boolean param_3 = obj.startsWith(prefix);
40+
*/

word-break/jinvicky.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import java.util.*;
2+
3+
class Solution {
4+
public boolean wordBreak(String s, List<String> wordDict) {
5+
int n = s.length();
6+
boolean[] dp = new boolean[n + 1]; // 빈 문자열을 대비해서 +1 길이로 설정
7+
dp[0] = true; // 빈 문자열은 항상 가능
8+
9+
for (int i = 0; i < n; i++) {
10+
if (!dp[i]) continue; // 여기까지 못 오면 확장 불가
11+
for (String w : wordDict) {
12+
int j = i + w.length();
13+
if (j <= n && s.startsWith(w, i)) {
14+
dp[j] = true;
15+
}
16+
}
17+
}
18+
return dp[n];
19+
}
20+
21+
public boolean wordBreak2(String s, List<String> wordDict) {
22+
int n = s.length();
23+
// List를 HashSet 객체 생성시 인자로 넣어 초기화가 가능합니다.
24+
// set은 List에서의 단어 탐색을 O(1) 성능으로 최적화하기 위해서 필요합니다.
25+
Set<String> wordSet = new HashSet<>(wordDict);
26+
27+
// 빈 문자열일 경우를 대비해서 +1을 길이로 설정합니다.
28+
boolean[] dp = new boolean[n + 1];
29+
// 빈 문자열을 항상 true이므로 0번째 dp[]에 true를 설정합니다.
30+
dp[0] = true;
31+
32+
// 앞서 빈문자열을 셋팅했으니 i는 1부터 n까지 반복합니다.
33+
// j는 0부터 i보다 작을때까지 반복합니다.
34+
/**
35+
* j=0: s.substring(0, 1) = "l" | dp[0]=true | "l" in dict=false
36+
* -----
37+
* j=0: s.substring(0, 2) = "le" | dp[0]=true | "le" in dict=false
38+
* j=1: s.substring(1, 2) = "e" | dp[1]=false | "e" in dict=false
39+
* -----
40+
* j=0: s.substring(0, 3) = "lee" | dp[0]=true | "lee" in dict=false
41+
* j=1: s.substring(1, 3) = "ee" | dp[1]=false | "ee" in dict=false
42+
* j=2: s.substring(2, 3) = "e" | dp[2]=false | "e" in dict=false
43+
* -----
44+
* j=0: s.substring(0, 4) = "leet" | dp[0]=true | "leet" in dict=true → dp[4] = true!
45+
* -----
46+
* j=0: s.substring(0, 5) = "leetc" | dp[0]=true | "leetc" in dict=false
47+
* j=1: s.substring(1, 5) = "eetc" | dp[1]=false | "eetc" in dict=false
48+
* j=2: s.substring(2, 5) = "etc" | dp[2]=false | "etc" in dict=false
49+
* j=3: s.substring(3, 5) = "tc" | dp[3]=false | "tc" in dict=false
50+
* j=4: s.substring(4, 5) = "c" | dp[4]=true | "c" in dict=false
51+
* -----
52+
* j=0: s.substring(0, 8) = "leetcode" | dp[0]=true | "leetcode" in dict=false
53+
* j=1: s.substring(1, 8) = "eetcode" | dp[1]=false | "eetcode" in dict=false
54+
* j=2: s.substring(2, 8) = "etcode" | dp[2]=false | "etcode" in dict=false
55+
* j=3: s.substring(3, 8) = "tcode" | dp[3]=false | "tcode" in dict=false
56+
* j=4: s.substring(4, 8) = "code" | dp[4]=true | "code" in dict=true → dp[8] = true!
57+
*/
58+
for (int i = 1; i <= n; i++) {
59+
for (int j = 0; j < i; j++) {
60+
// 예시를 보면 (start, end)에서 start가 증가할 동안 end는 고정됩니다. 따라서 start=j, end=i가 되어야 합니다.
61+
if (dp[j] && wordSet.contains(s.substring(j, i))) {
62+
dp[i] = true;
63+
break;
64+
}
65+
}
66+
}
67+
return dp[n];
68+
}
69+
}

word-search/jinvicky.java

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,27 @@ class Solution {
22
public boolean exist(char[][] board, String word) {
33
int m = board.length;
44
int n = board[0].length;
5-
for(int i = 0; i < m; i++) {
6-
for(int j = 0; j < n; j++) {
7-
if(dfs(board, i, j, word, 0)) {
5+
for (int i = 0; i < m; i++) {
6+
for (int j = 0; j < n; j++) {
7+
if (dfs(board, i, j, word, 0)) {
88
return true;
99
}
1010
}
1111
}
1212
return false;
1313
}
1414

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

2020
char temp = board[i][j];
2121
board[i][j] = '#';
22-
boolean found = dfs(board, i+1, j, word, index+1)
23-
|| dfs(board, i-1, j, word, index+1)
24-
|| dfs(board, i, j+1, word, index+1)
25-
|| dfs(board, i, j-1, word, index+1);
22+
boolean found = dfs(board, i + 1, j, word, index + 1)
23+
|| dfs(board, i - 1, j, word, index + 1)
24+
|| dfs(board, i, j + 1, word, index + 1)
25+
|| dfs(board, i, j - 1, word, index + 1);
2626

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

51+
// 백트래킹 인자 국룰 (인자로 받는다는 건 다음에 필요함 + 다음 재귀때 업데이트된 값이 필요해서)
52+
// 재귀여야 하기 때문에 2차원 input과 방향을 위한 i, j를 받도록 한다.
53+
// 매번 조건 일치를 확인하기 위한 target 값이 인자로 포함된다 (문제마다 다름)
54+
55+
// private boolean backtrack (원본 2차원 배열, 타겟, 2차원 방문 배열, i 방향 인덱스, j방향 인덱스, 조건부 target) {
56+
// break1. 조건을 만족하면 값을 반환
57+
// break2. i와 j의 범위가 0보다 작거나 원본 배열의 길이보다 크거나 같으면 탈락
58+
// break3. 방문배열[i][j]가 true거나 (이미 방문했음) 또는 문제의 만족 조건이 아닐 경우 탈락
59+
// 보통 break2, break3을 한번의 if문으로 작성하지만 난 그마저도 어려워서 한번 더 분리했다.
60+
//
61+
// action1. 방문배열[i][j] 를 true로 설정한다.
62+
// action2. i와 j를 위한 2차원 방향배열 템플릿을 선언한다.
63+
// action3. 방향은 무조건 동서남북 4방향 고정이다. 4만큼 반복되는 for문을 실행한다.
64+
// action3-1. dir[0], dir[1]을 i와 j에게 더해서 nextI, nextJ로 만들어 backtrack()에 전달한다. (이때 조건이 맞으면 return으로 break)
65+
// action4. return을 못한 경우 방문배열[i][j]를 false로 재설정한다.
66+
// action5. false 등을 리턴한다.
67+
// }
68+
69+
5170
private boolean backtrack(char[][] board, String word, boolean[][] visited, int i, int j, int index) {
5271
if (index == word.length()) {
5372
return true;
5473
}
5574

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

6284
visited[i][j] = true;
6385

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

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

0 commit comments

Comments
 (0)