Skip to content

[GangBean] Week 5 #846

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

Merged
merged 5 commits into from
Jan 10, 2025
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/GangBean.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class Solution {
public int maxProfit(int[] prices) {
/**
1. understanding
- price[i]: i th day's stock price
- to maximize profit, choose a single day to buy, and future day to sell.
- return maximum profit
- [7, 1, 5, 3, 6, 4] -> [0, 0, 4, 4, 5, 5]
- [7, 6, 4, 3, 1] -> [0, 0, 0, 0, 0]
2. strategy
- profit = (sell price) - (buy price)
3. complexity
- time: O(N)
- space: O(1)
*/
int minPrice = prices[0];
for (int i = 0; i <prices.length; i++) {
int tmp = prices[i];
if (i == 0) {
prices[i] = 0;
} else {
prices[i] = Math.max(prices[i-1], prices[i] - minPrice);
Copy link
Member

Choose a reason for hiding this comment

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

입력 배열을 변경하셨군요. 신선한 접근입니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 이 문제는 추가 공간 할당 없이 처리 가능하더라구요 😀

}
minPrice = Math.min(minPrice, tmp);
}

return prices[prices.length - 1];
}
}

52 changes: 52 additions & 0 deletions encode-and-decode-strings/GangBean.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import java.util.*;

public class Codec {
/**
1. complexity:
- time: O(N * L), where N is the length of strs, L is maximum length of each word in strs
- space: O(N * L)
*/

// Encodes a list of strings to a single string.
public String encode(List<String> strs) {
// 필요한 정보: 전체 원본 문자열, 각 단어의 위치와 길이
StringBuilder origin = new StringBuilder();
StringJoiner meta = new StringJoiner("/");
StringJoiner encoded = new StringJoiner(";");
int startIdx = 0;
for (String word: strs) { // O(N)
origin.append(word);
meta.add(String.format("(%d,%d)", startIdx, word.length()));
startIdx += word.length();
}

encoded.add(origin.toString()).add(meta.toString());
return encoded.toString();
}

// Decodes a single string to a list of strings.
public List<String> decode(String s) {
List<String> ret = new ArrayList<>();
int delimeterIdx = s.lastIndexOf(";"); // O(N * L)
Copy link
Contributor

Choose a reason for hiding this comment

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

한가지 궁금한점이 있습니다. 현재 encode 함수의 return 값은 다음과 같을 것 같아요.

["hello", "world"] // Input
"helloworld;(0,5)/(5,5)" // Output

따라서 decode 과정에서 s 의 뒤에서 부터 ; 를 찾는 과정이 있는 것으로 보입니다. 이 때문에 단어의 길이에 비례하여 ; 를 찾는 시간이 길어질 것 같아요. 물론 전체 시간 복잡도에는 영향이 없지만, 만약 중간중간에 단어의 meta 정보를 넣는다면 이 과정이 생략될 수도 있을 것 같아서요, 혹시 meta 정보를 아예 뒤로 분리하신 이유가 있으실까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

음 일단 meta 정보를 뒤로 분리한 이유는, 본문과 meta 정보를 구분하는데에 위치 정보(처음 혹은 마지막)가 필수라고 생각해서였습니다!
메타 정보와 본문을 분리하기 위해선 구분자, 혹은 공통된 format의 메세지 구조를 정의해야, 이를 통해 각 본문과 메타정보 페어를 각각 분리해 처리할 수 있을텐데, 어떤 형태로 format을 정의해도 본문에 동일한 구조의 문자가 나올 수 있기 때문에, 이 정보들만으론 알맞은 페어 단위로 분리할 수 없다고 생각했습니다.
위치 정보를 추가한다면 명확한 구분 기준을 부여할 수 있을거라 생각해 뒤쪽으로 메타정보를 몰아두는 방식으로 구현했습니다.

말씀해주신대로 메타정보를 각 본문과 연결해 구현하는 방식이 가능하다면, 제가 구현한 방법이 메타 정보 문자열의 길이 만큼의 탐색이 추가로 필요한 비효율이 있는 것 같습니다!

String origin = s.substring(0, delimeterIdx); // O(N * L)
String meta = s.substring(delimeterIdx+1); // O(N * L)
String[] wordInfos = meta.split("/");
for (String wordInfo: wordInfos) { // O(N)
delimeterIdx = wordInfo.indexOf(","); // O(1)
int length = Integer.parseInt(wordInfo.substring(delimeterIdx+1, wordInfo.length() - 1)); // O(1)
String word = "";
if (length > 0) {
int startIdx = Integer.parseInt(wordInfo.substring(1, delimeterIdx));
word = origin.substring(startIdx, startIdx + length);
}
ret.add(word);
}
return ret;
}
}

// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.decode(codec.encode(strs));


37 changes: 37 additions & 0 deletions group-anagrams/GangBean.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
/**
1. understanding
- grouping the anagrams together, and return groups
2. strategy
- anagram group's identity: same characters with same counts
- so, transform each strs to character and count hashtable, called 'id'.
- if groups contains strs's 'id', then append
- return values list
3. complexity
- time: O(N * L) where, N is the length of array strs, and L is the max length of each str
- space: O(N * L)
*/
Map<Map<Character, Integer>, List<String>> groups = new HashMap<>();
for (String word: strs) {
Map<Character, Integer> id = idOf(word);
List<String> group = groups.getOrDefault(id, new ArrayList<>());
group.add(word);
groups.put(id, group);
}

// System.out.println(groups);
List<List<String>> ret = new ArrayList<>();
ret.addAll(groups.values());
return ret;
Comment on lines +23 to +26
Copy link
Member

Choose a reason for hiding this comment

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

요렇게 하시면 좀 더 깔끔할 것 같습니다.

Suggested change
// System.out.println(groups);
List<List<String>> ret = new ArrayList<>();
ret.addAll(groups.values());
return ret;
return new ArrayList<>(groups.values());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

감사합니다 말씀해주신대로 입력파라미터 생성자로 처리하는게 코드 가독성이 좋을것 같네요

}

private Map<Character, Integer> idOf(String word) {
Map<Character, Integer> id = new HashMap<>();
for (char c: word.toCharArray()) {
id.put(c, id.getOrDefault(c, 0) + 1);
}
return id;
}
}

52 changes: 52 additions & 0 deletions implement-trie-prefix-tree/GangBean.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
class Trie {
/**
1. understanding
- Trie data structure
- To process at most 3 * 10^4 calls in proper time, each call must be under O(N), where N is the length of word.
- insert: insert data into Trie
- search: find data from inserted Datas
- startsWith: find
2. strategy
- a) use list to save inserted words
- insert: O(1)
- search: O(L * N), where L is the length of list, N is the length of each words.
- startsWith: O(L * N)
Comment on lines +11 to +13
Copy link
Member

Choose a reason for hiding this comment

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

insert()의 성능을 희생해서 search()startsWith() 성능을 올릴 수 있는 길은 없을까요?

- total call to be 3 * 10^4, i assume each method call count at most 10^4 or linear correlation in 10^4 scale.
- so, L <= 10^4, N <= 10^4
- space: O(L * N)
- it is enough to pass, but there are duplicates in space and also in excution count.
- b)
*/

private List<String> words;
public Trie() {
words = new ArrayList<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

현재 구현해 주신 방식으로 기준으로 보았을 때 words 를 배열보다는 Dictionary 형태가 더 효율적일 수도 있을 것이라 생각이 들었어요. 즉, Array 에서 search 를 진행할 때 특정 word 를 찾기 위해서는 O(n) 이 걸리지만, Dictionary 로 관리하면 O(1) 이 걸리기 때문인데요, 혹시 Array 를 사용해주신 이유가 있으실까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

맞습니다!
풀이 관점에서 list를 사용한 기술적인 이유는 없구, 말씀해주신대로 hashtable 형태의 자료구조를 사용하는게 search 시 성능이 더 좋을거라는데 동의합니다!
다만, 가장 먼저 떠오른 자료구조였고, 더 나은 알고리즘(시간, 공간복잡도 관점)으로 나아가기 위한 문제점을 찾아 정의하고 개선해나가는 연습을 하기 위해서 굳이 list를 사용해 풀이를 진행했습니다 😄

}

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

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

public boolean startsWith(String prefix) {
for (String w: words) {
if (w.indexOf(prefix) == 0) 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);
*/

34 changes: 34 additions & 0 deletions word-break/GangBean.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
/**
1. understanding
- check if s's segments are all in wordDict.
- wordDict can be used multiple times
2. strategy
a) dynamic programming
- dp[k]: substring(0,k+1) can be constructed by wordDict.
- dp[0]: false
- dp[1]: substring(0, 2) in wordDict
- dp[k+1] = substring(0, k+2) in wordDict || Any(dp[k] && substring(k+1, k+2) in wordDict)
- return dp[s.length()]
3. complexity
- time: O(N^2 * S), where N is the length of s, S is search time each segment in wordDict. You can calculate S in O(1) time, when change wordDict to Set. so O(N) is final time complexity.
- space: O(N + W), W is the size of wordDict
*/
Set<String> bow = new HashSet<>(wordDict);
boolean[] dp = new boolean[s.length() + 1];

for (int i = 1; i < dp.length; i++) { // O(N)
String segment = s.substring(0, i);
dp[i] = bow.contains(segment);
for (int j = 0; j < i; j++) { // O(N)
if (dp[i]) break;
segment = s.substring(j, i);
dp[i] = dp[i] || (dp[j] && bow.contains(segment)); // O(1)
}
}

return dp[s.length()];
}
}

Loading