-
-
Notifications
You must be signed in to change notification settings - Fork 361
[okyungjin] WEEK 05 Solutions #2763
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
Changes from all commits
af62510
39b3942
615fffb
26972dc
56d4bfc
3ab45db
a0a7154
52ec553
bfc447f
84fbf28
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
okyungjin marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| ''' | ||
| [문제] | ||
| - 주식 가격이 담긴 정수 배열 prices가 주어진다. | ||
| - 특정 날짜에 주식을 단 한 번 구매하고, 미래의 특정 날짜에 판매하여 얻을 수 있는 최대 이익을 구한다. | ||
| - 이익을 낼 수 없는 경우 0을 반환하며, 반드시 구매 후 판매해야 한다. | ||
|
|
||
| [풀이] | ||
| 1. prices 배열을 순회하며 최소 가격을 min_price에 기록한다. | ||
| 2. 동시에 현재 가격에서 min_price를 뺀 이익이 최대인지 확인하여 갱신한다. | ||
| 3. 최대 이익을 반환한다. | ||
|
|
||
| [복잡도] | ||
| 시간 복잡도: O(N) | ||
| 공간 복잡도: O(1) | ||
| ''' | ||
| class Solution: | ||
| def maxProfit(self, prices): | ||
| min_price = prices[0] | ||
| max_profit = 0 | ||
|
|
||
| for i in range(1, len(prices)): | ||
| if prices[i] < min_price: | ||
| min_price = prices[i] | ||
|
|
||
| elif prices[i] - min_price > max_profit: | ||
| max_profit = prices[i] - min_price | ||
|
|
||
| return max_profit |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 덕분에
okyungjin marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| """ | ||
| Design an algorithm to encode a list of strings to a string. | ||
| The encoded string is then sent over the network and is decoded back to the original list of strings. | ||
|
|
||
| Example 1: | ||
| Input: List[str] = ["Hello","World"] | ||
| Output: ["Hello","World"] | ||
|
|
||
| Example 2: | ||
| Input: List[str] = [] | ||
| Output: [] | ||
|
|
||
| Constraints: | ||
| 0 <= strs.length < 100 | ||
| 0 <= strs[i].length < 200 | ||
| strs[i] contains any possible characters out of 256 valid ASCII characters. | ||
| """ | ||
| from typing import Final, List | ||
|
|
||
| """ | ||
| 접근법: | ||
| encode: 각 단어 앞에 글자수 + 구분자를 붙여 하나의 문자열로 이어 붙입니다. | ||
| decode: 문자열을 앞에서부터 읽으면서 구분자를 찾고, 그 앞의 숫자로 '글자수'를 알아낸 뒤 정확히 그 길이만큼만 잘라내어 원래 리스트로 복원합니다. | ||
|
|
||
| 복잡도: | ||
| 시간 복잡도: O(N) | ||
| 공간 복잡도: O(N) | ||
| """ | ||
| class Solution: | ||
| # 구분자 심볼 | ||
| DELIMITER: Final[str] = '#' | ||
|
|
||
| """ | ||
| `글자수 + 구분자 + 단어` 조합으로 인코딩한 후 이어붙여 반환합니다. | ||
|
|
||
| Example: | ||
| Input: ["Hello","World"] | ||
| Return: "5#Hello5#World" | ||
|
|
||
| Input: [] | ||
| Return: "" | ||
|
|
||
| Input: [""] | ||
| Return: "0#" | ||
| """ | ||
| def encode(self, strs: List[str]) -> str: | ||
| return ''.join(f"{len(s)}{self.DELIMITER}{s}" for s in strs) | ||
|
|
||
|
|
||
| """ | ||
| 1. 현재 위치부터 탐색하여 첫 번째 구분자(#)의 위치를 찾습니다. | ||
| 2. 구분자 바로 앞의 숫자를 읽어내어 잘라낼 단어의 길이를 파악합니다. | ||
| 3. 구분자 다음 위치부터 파악한 길이만큼 문자열을 잘라내어 결과 리스트에 담습니다. | ||
| 4. 문자열 끝까지 위 과정을 반복한 후, 최종 리스트를 반환합니다. | ||
| """ | ||
| def decode(self, s: str) -> List[str]: | ||
| result = [] | ||
| idx = 0 | ||
|
|
||
| while idx < len(s): | ||
| delimiter_idx = s.find(self.DELIMITER, idx) | ||
| length = int(s[idx : delimiter_idx]) | ||
|
|
||
| idx = delimiter_idx + 1 + length | ||
| result.append(s[delimiter_idx + 1 : idx]) | ||
|
|
||
| return result |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sorted를 쓰는것도 좋지만, 정렬 자체가 N Log N 시간복잡도를 가져서
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@alphaorderly 감사합니다 ㅎㅎ 문제 쫙 풀고 최적화 해보도록 하겠습니다 👍👍👍
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 카운터 리스트를 튜플로 변환한 다음에 키로 사용하도록 최적화했습니다.
okyungjin marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| ''' | ||
| [복잡도] | ||
| n: strs의 길이, k: 각 문자열의 최대 길이 | ||
|
|
||
| 시간 복잡도: O(n * k log k) | ||
| 공간 복잡도: O(n * k) | ||
| ''' | ||
| class Solution: | ||
| def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||
| anagram_map: dict[str, List[str]] = {} | ||
|
|
||
| for s in strs: | ||
| key = ''.join(sorted(s)) | ||
|
|
||
| if key in anagram_map: | ||
| anagram_map[key].append(s) | ||
| else: | ||
| anagram_map[key] = [s] | ||
|
|
||
| return list(anagram_map.values()) | ||
|
|
||
|
|
||
| ''' | ||
| solution2: 문자열의 빈도수 배열을 해시의 키로 사용 | ||
|
|
||
| 복잡도: | ||
| n: strs의 길이, k: 각 문자열의 최대 길이 | ||
| 시간 복잡도: O(n * k) | ||
| 공간 복잡도: O(n * k) | ||
| ''' | ||
| from collections import defaultdict | ||
|
|
||
| class Solution: | ||
| def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||
| anagram_map: dict[tuple, List[str]] = defaultdict(list) | ||
|
|
||
| for s in strs: | ||
| counts = [0] * 26 | ||
| for char in s: | ||
| counts[ord(char) - ord('a')] += 1 | ||
|
|
||
| anagram_map[tuple(counts)].append(s) | ||
|
|
||
| return list(anagram_map.values()) |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 문제의 목표에는 꽤나 잘 맞도록 잘 구현해 주셨는데요! 해당 자료구조는 tree의 형태에 좀 더 가까운 자료구조고, 지금처럼 모든 prefix의 경우를 저장하시지 않으시더라도 최적의 시간으로 구할수 있게 해줘서 좋아요!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@alphaorderly 오 맞아요... 사실 문제를 풀고나서 위키 문서를 보게 되었는데 최초에 생각한 구현이랑 방향성이 많이 다르더라구요. 내일 저녁 먹고 이어서 풀어보겠습니다 감사합니다 🔥
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 52ec553 커밋에서 적용했습니다.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 두 개의 구현하신 클래스가 공간 복잡도는 높게 나오지만, 조회 성능이 중요한 문제에서는 좋을 것 같네요!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 직관적으로 떠오른 풀이인데 문제의 요구사항이랑은 멀더라구요.
okyungjin marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| class Trie: | ||
|
|
||
| def __init__(self): | ||
| self.word_set = set() | ||
| self.prefix_set = set() | ||
|
|
||
|
|
||
| def insert(self, word: str) -> None: | ||
| if word in self.word_set: | ||
| return | ||
|
|
||
| self.word_set.add(word) | ||
|
|
||
| prefix = '' | ||
| for char in word: | ||
| prefix += char | ||
| self.prefix_set.add(prefix) | ||
|
|
||
|
|
||
| def search(self, word: str) -> bool: | ||
| return word in self.word_set | ||
|
|
||
|
|
||
| def startsWith(self, prefix: str) -> bool: | ||
| return prefix in self.prefix_set | ||
|
|
||
|
|
||
| ''' | ||
| solution2: TrieNode 자료구조 활용 | ||
| ''' | ||
| class TrieNode: | ||
| def __init__(self): | ||
| self.children: dict[str, 'TrieNode'] = {} | ||
| self.is_leaf = False | ||
|
|
||
| class Trie: | ||
| def __init__(self): | ||
| self.root = TrieNode() | ||
|
|
||
| ''' | ||
| 시간 복잡도: O(L), L: 단어의 길이 | ||
| 공간 복잡도: O(L), 겹치는 문자열이 없을 때 TrideNode N개 생성 | ||
| ''' | ||
| def insert(self, word: str) -> None: | ||
| node = self.root | ||
|
|
||
| for char in word: | ||
| if char not in node.children: | ||
| node.children[char] = TrieNode() | ||
| node = node.children[char] | ||
|
|
||
| node.is_leaf = True | ||
|
|
||
| ''' | ||
| 시간 복잡도: O(L), L: 단어의 길이 | ||
| 공간 복잡도: O(1) | ||
| ''' | ||
| def search(self, word: str) -> bool: | ||
| node = self.root | ||
|
|
||
| for char in word: | ||
| if char not in node.children: | ||
| return False | ||
| node = node.children[char] | ||
|
|
||
| return node.is_leaf | ||
|
|
||
| ''' | ||
| 시간 복잡도: O(L), L: 단어의 길이 | ||
| 공간 복잡도: O(1) | ||
| ''' | ||
| def startsWith(self, prefix: str) -> bool: | ||
| node = self.root | ||
|
|
||
| for char in prefix: | ||
| if char not in node.children: | ||
| return False | ||
| node = node.children[char] | ||
|
|
||
| return True |
|
okyungjin marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 와우 다양한 방법으로 풀이를 해주셨네요.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| ''' | ||
| solution1: dfs + @cache | ||
| ''' | ||
| from typing import List | ||
| from functools import cache | ||
|
|
||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
| size = len(s) | ||
| word_set = set(wordDict) | ||
|
|
||
| @cache | ||
| def dfs(start: int) -> bool: | ||
| if start == size: | ||
| return True | ||
|
|
||
| for end in range(start + 1, size + 1): | ||
| word = s[start:end] # O(N) | ||
|
|
||
| if word in word_set and dfs(end): | ||
| return True | ||
|
|
||
| return False | ||
|
|
||
| return dfs(0) | ||
|
|
||
| ''' | ||
| solution2: dfs + memo | ||
| ''' | ||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: list[str]) -> bool: | ||
| size = len(s) | ||
| word_set = set(wordDict) | ||
| failed: set[int] = set() # 탐색에 실패한 인덱스 기록 | ||
|
|
||
| def dfs(start: int) -> bool: | ||
| if start == size: | ||
| return True | ||
|
|
||
| if start in failed: | ||
| return False | ||
|
|
||
| for end in range(start + 1, size + 1): | ||
| word = s[start:end] | ||
|
|
||
| if word in word_set and dfs(end): | ||
| return True | ||
|
|
||
| failed.add(start) | ||
| return False | ||
|
|
||
| return dfs(0) | ||
|
|
||
| ''' | ||
| solution3: bfs | ||
| ''' | ||
| from collections import deque | ||
|
|
||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: list[str]) -> bool: | ||
| size = len(s) | ||
| word_set = set(wordDict) | ||
| queue = deque([0]) | ||
| visited = set([0]) | ||
|
|
||
| while queue: | ||
| start = queue.popleft() | ||
|
|
||
| for end in range(start + 1, size + 1): | ||
| word = s[start:end] | ||
|
|
||
| if end not in visited and word in word_set: | ||
| if end == size: | ||
| return True | ||
|
|
||
| queue.append(end) | ||
| visited.add(end) | ||
|
|
||
| return False | ||
|
|
||
| ''' | ||
| solution4: dp | ||
| ''' | ||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: list[str]) -> bool: | ||
| size = len(s) | ||
| word_set = set(wordDict) | ||
| dp = [False] * (size + 1) | ||
| dp[0] = True | ||
|
|
||
| for end in range(1, size + 1): | ||
| for start in range(end): | ||
| word = s[start:end] | ||
|
|
||
| if dp[start] and word in word_set: | ||
| dp[end] = True | ||
| break | ||
|
|
||
| return dp[-1] | ||
|
|
||
| ''' | ||
| solution5: trie 자료구조 활용 | ||
|
|
||
| Trie 자료구조로 노드를 만들어서 탐색한다 | ||
| Examples3: s = "catsandog", wordDict = ["cats","dog","sand","and","cat"] | ||
|
|
||
| [Root] | ||
| ├── 'c' ── 'a' ── 't'(leaf) ── 's'(leaf) | ||
| ├── 'd' ── 'o' ── 'g'(leaf) | ||
| ├── 's' ── 'a' ── 'n' ── 'd'(leaf) | ||
| └── 'a' ── 'n' ── 'd'(leaf) | ||
| ''' | ||
| class TrieNode: | ||
| def __init__(self): | ||
| self.children: dict[int, 'TrieNode'] = {} | ||
| self.is_leaf = False | ||
|
|
||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: list[str]) -> bool: | ||
| root = self._build_trie(wordDict) | ||
|
|
||
| size = len(s) | ||
| dp = [False] * (size + 1) | ||
| dp[0] = True | ||
|
|
||
| for start in range(size): | ||
| if not dp[start]: | ||
| continue | ||
|
|
||
| node = root | ||
| for end in range(start + 1, size + 1): | ||
| char = s[end - 1] | ||
| # trie에 없으면 바로 종료 | ||
| if char not in node.children: | ||
| break | ||
| node = node.children[char] | ||
|
|
||
| if node.is_leaf: | ||
| dp[end] = True | ||
|
|
||
| return dp[-1] | ||
|
|
||
| # wordDict로 trie 자료구조 생성 | ||
| def _build_trie(self, wordDict: list[str]) -> TrieNode: | ||
| root = TrieNode() | ||
|
|
||
| for word in wordDict: | ||
| node = root | ||
|
|
||
| for char in word: | ||
| if char not in node.children: | ||
| node.children[char] = TrieNode() | ||
| node = node.children[char] | ||
| node.is_leaf = True | ||
|
|
||
| return root |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엄청 깔끔하게 잘 해결하셨네요!
best time to buy and sell stock, 즉 해당 문제는
뒤에 로마 숫자를 붙혀서 1, 2, 3, 4, 5 총 다섯종류가 있는데요
dp 연습하기에 정말 괜찮은 문제라고 생각해서
2번문제
II는 한번 풀어보시길 추천드려요!