Skip to content

Commit ce7cc62

Browse files
authored
[okyungjin] WEEK 05 Solutions (#2763)
* 121. Best Time to Buy and Sell Stock * 49. Group Anagrams * 208. Implement Trie (Prefix Tree) * 208. Implement Trie (Prefix Tree) - fix linelint * Encode and Decode Strings * Encode and Decode Strings (타입 및 주석 수정) * 139. Word Break * 208. Implement Trie (Prefix Tree) - TrieNode 자료구조 활용 * 139. Word Break - Trie 자료구조 활용 * 49. Group Anagrams - 시간 복잡도 최적화
1 parent f4879f9 commit ce7cc62

5 files changed

Lines changed: 375 additions & 0 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'''
2+
[문제]
3+
- 주식 가격이 담긴 정수 배열 prices가 주어진다.
4+
- 특정 날짜에 주식을 단 한 번 구매하고, 미래의 특정 날짜에 판매하여 얻을 수 있는 최대 이익을 구한다.
5+
- 이익을 낼 수 없는 경우 0을 반환하며, 반드시 구매 후 판매해야 한다.
6+
7+
[풀이]
8+
1. prices 배열을 순회하며 최소 가격을 min_price에 기록한다.
9+
2. 동시에 현재 가격에서 min_price를 뺀 이익이 최대인지 확인하여 갱신한다.
10+
3. 최대 이익을 반환한다.
11+
12+
[복잡도]
13+
시간 복잡도: O(N)
14+
공간 복잡도: O(1)
15+
'''
16+
class Solution:
17+
def maxProfit(self, prices):
18+
min_price = prices[0]
19+
max_profit = 0
20+
21+
for i in range(1, len(prices)):
22+
if prices[i] < min_price:
23+
min_price = prices[i]
24+
25+
elif prices[i] - min_price > max_profit:
26+
max_profit = prices[i] - min_price
27+
28+
return max_profit
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""
2+
Design an algorithm to encode a list of strings to a string.
3+
The encoded string is then sent over the network and is decoded back to the original list of strings.
4+
5+
Example 1:
6+
Input: List[str] = ["Hello","World"]
7+
Output: ["Hello","World"]
8+
9+
Example 2:
10+
Input: List[str] = []
11+
Output: []
12+
13+
Constraints:
14+
0 <= strs.length < 100
15+
0 <= strs[i].length < 200
16+
strs[i] contains any possible characters out of 256 valid ASCII characters.
17+
"""
18+
from typing import Final, List
19+
20+
"""
21+
접근법:
22+
encode: 각 단어 앞에 글자수 + 구분자를 붙여 하나의 문자열로 이어 붙입니다.
23+
decode: 문자열을 앞에서부터 읽으면서 구분자를 찾고, 그 앞의 숫자로 '글자수'를 알아낸 뒤 정확히 그 길이만큼만 잘라내어 원래 리스트로 복원합니다.
24+
25+
복잡도:
26+
시간 복잡도: O(N)
27+
공간 복잡도: O(N)
28+
"""
29+
class Solution:
30+
# 구분자 심볼
31+
DELIMITER: Final[str] = '#'
32+
33+
"""
34+
`글자수 + 구분자 + 단어` 조합으로 인코딩한 후 이어붙여 반환합니다.
35+
36+
Example:
37+
Input: ["Hello","World"]
38+
Return: "5#Hello5#World"
39+
40+
Input: []
41+
Return: ""
42+
43+
Input: [""]
44+
Return: "0#"
45+
"""
46+
def encode(self, strs: List[str]) -> str:
47+
return ''.join(f"{len(s)}{self.DELIMITER}{s}" for s in strs)
48+
49+
50+
"""
51+
1. 현재 위치부터 탐색하여 첫 번째 구분자(#)의 위치를 찾습니다.
52+
2. 구분자 바로 앞의 숫자를 읽어내어 잘라낼 단어의 길이를 파악합니다.
53+
3. 구분자 다음 위치부터 파악한 길이만큼 문자열을 잘라내어 결과 리스트에 담습니다.
54+
4. 문자열 끝까지 위 과정을 반복한 후, 최종 리스트를 반환합니다.
55+
"""
56+
def decode(self, s: str) -> List[str]:
57+
result = []
58+
idx = 0
59+
60+
while idx < len(s):
61+
delimiter_idx = s.find(self.DELIMITER, idx)
62+
length = int(s[idx : delimiter_idx])
63+
64+
idx = delimiter_idx + 1 + length
65+
result.append(s[delimiter_idx + 1 : idx])
66+
67+
return result

group-anagrams/okyungjin.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'''
2+
[복잡도]
3+
n: strs의 길이, k: 각 문자열의 최대 길이
4+
5+
시간 복잡도: O(n * k log k)
6+
공간 복잡도: O(n * k)
7+
'''
8+
class Solution:
9+
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
10+
anagram_map: dict[str, List[str]] = {}
11+
12+
for s in strs:
13+
key = ''.join(sorted(s))
14+
15+
if key in anagram_map:
16+
anagram_map[key].append(s)
17+
else:
18+
anagram_map[key] = [s]
19+
20+
return list(anagram_map.values())
21+
22+
23+
'''
24+
solution2: 문자열의 빈도수 배열을 해시의 키로 사용
25+
26+
복잡도:
27+
n: strs의 길이, k: 각 문자열의 최대 길이
28+
시간 복잡도: O(n * k)
29+
공간 복잡도: O(n * k)
30+
'''
31+
from collections import defaultdict
32+
33+
class Solution:
34+
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
35+
anagram_map: dict[tuple, List[str]] = defaultdict(list)
36+
37+
for s in strs:
38+
counts = [0] * 26
39+
for char in s:
40+
counts[ord(char) - ord('a')] += 1
41+
42+
anagram_map[tuple(counts)].append(s)
43+
44+
return list(anagram_map.values())
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
class Trie:
2+
3+
def __init__(self):
4+
self.word_set = set()
5+
self.prefix_set = set()
6+
7+
8+
def insert(self, word: str) -> None:
9+
if word in self.word_set:
10+
return
11+
12+
self.word_set.add(word)
13+
14+
prefix = ''
15+
for char in word:
16+
prefix += char
17+
self.prefix_set.add(prefix)
18+
19+
20+
def search(self, word: str) -> bool:
21+
return word in self.word_set
22+
23+
24+
def startsWith(self, prefix: str) -> bool:
25+
return prefix in self.prefix_set
26+
27+
28+
'''
29+
solution2: TrieNode 자료구조 활용
30+
'''
31+
class TrieNode:
32+
def __init__(self):
33+
self.children: dict[str, 'TrieNode'] = {}
34+
self.is_leaf = False
35+
36+
class Trie:
37+
def __init__(self):
38+
self.root = TrieNode()
39+
40+
'''
41+
시간 복잡도: O(L), L: 단어의 길이
42+
공간 복잡도: O(L), 겹치는 문자열이 없을 때 TrideNode N개 생성
43+
'''
44+
def insert(self, word: str) -> None:
45+
node = self.root
46+
47+
for char in word:
48+
if char not in node.children:
49+
node.children[char] = TrieNode()
50+
node = node.children[char]
51+
52+
node.is_leaf = True
53+
54+
'''
55+
시간 복잡도: O(L), L: 단어의 길이
56+
공간 복잡도: O(1)
57+
'''
58+
def search(self, word: str) -> bool:
59+
node = self.root
60+
61+
for char in word:
62+
if char not in node.children:
63+
return False
64+
node = node.children[char]
65+
66+
return node.is_leaf
67+
68+
'''
69+
시간 복잡도: O(L), L: 단어의 길이
70+
공간 복잡도: O(1)
71+
'''
72+
def startsWith(self, prefix: str) -> bool:
73+
node = self.root
74+
75+
for char in prefix:
76+
if char not in node.children:
77+
return False
78+
node = node.children[char]
79+
80+
return True

word-break/okyungjin.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
'''
2+
solution1: dfs + @cache
3+
'''
4+
from typing import List
5+
from functools import cache
6+
7+
class Solution:
8+
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
9+
size = len(s)
10+
word_set = set(wordDict)
11+
12+
@cache
13+
def dfs(start: int) -> bool:
14+
if start == size:
15+
return True
16+
17+
for end in range(start + 1, size + 1):
18+
word = s[start:end] # O(N)
19+
20+
if word in word_set and dfs(end):
21+
return True
22+
23+
return False
24+
25+
return dfs(0)
26+
27+
'''
28+
solution2: dfs + memo
29+
'''
30+
class Solution:
31+
def wordBreak(self, s: str, wordDict: list[str]) -> bool:
32+
size = len(s)
33+
word_set = set(wordDict)
34+
failed: set[int] = set() # 탐색에 실패한 인덱스 기록
35+
36+
def dfs(start: int) -> bool:
37+
if start == size:
38+
return True
39+
40+
if start in failed:
41+
return False
42+
43+
for end in range(start + 1, size + 1):
44+
word = s[start:end]
45+
46+
if word in word_set and dfs(end):
47+
return True
48+
49+
failed.add(start)
50+
return False
51+
52+
return dfs(0)
53+
54+
'''
55+
solution3: bfs
56+
'''
57+
from collections import deque
58+
59+
class Solution:
60+
def wordBreak(self, s: str, wordDict: list[str]) -> bool:
61+
size = len(s)
62+
word_set = set(wordDict)
63+
queue = deque([0])
64+
visited = set([0])
65+
66+
while queue:
67+
start = queue.popleft()
68+
69+
for end in range(start + 1, size + 1):
70+
word = s[start:end]
71+
72+
if end not in visited and word in word_set:
73+
if end == size:
74+
return True
75+
76+
queue.append(end)
77+
visited.add(end)
78+
79+
return False
80+
81+
'''
82+
solution4: dp
83+
'''
84+
class Solution:
85+
def wordBreak(self, s: str, wordDict: list[str]) -> bool:
86+
size = len(s)
87+
word_set = set(wordDict)
88+
dp = [False] * (size + 1)
89+
dp[0] = True
90+
91+
for end in range(1, size + 1):
92+
for start in range(end):
93+
word = s[start:end]
94+
95+
if dp[start] and word in word_set:
96+
dp[end] = True
97+
break
98+
99+
return dp[-1]
100+
101+
'''
102+
solution5: trie 자료구조 활용
103+
104+
Trie 자료구조로 노드를 만들어서 탐색한다
105+
Examples3: s = "catsandog", wordDict = ["cats","dog","sand","and","cat"]
106+
107+
[Root]
108+
├── 'c' ── 'a' ── 't'(leaf) ── 's'(leaf)
109+
├── 'd' ── 'o' ── 'g'(leaf)
110+
├── 's' ── 'a' ── 'n' ── 'd'(leaf)
111+
└── 'a' ── 'n' ── 'd'(leaf)
112+
'''
113+
class TrieNode:
114+
def __init__(self):
115+
self.children: dict[int, 'TrieNode'] = {}
116+
self.is_leaf = False
117+
118+
class Solution:
119+
def wordBreak(self, s: str, wordDict: list[str]) -> bool:
120+
root = self._build_trie(wordDict)
121+
122+
size = len(s)
123+
dp = [False] * (size + 1)
124+
dp[0] = True
125+
126+
for start in range(size):
127+
if not dp[start]:
128+
continue
129+
130+
node = root
131+
for end in range(start + 1, size + 1):
132+
char = s[end - 1]
133+
# trie에 없으면 바로 종료
134+
if char not in node.children:
135+
break
136+
node = node.children[char]
137+
138+
if node.is_leaf:
139+
dp[end] = True
140+
141+
return dp[-1]
142+
143+
# wordDict로 trie 자료구조 생성
144+
def _build_trie(self, wordDict: list[str]) -> TrieNode:
145+
root = TrieNode()
146+
147+
for word in wordDict:
148+
node = root
149+
150+
for char in word:
151+
if char not in node.children:
152+
node.children[char] = TrieNode()
153+
node = node.children[char]
154+
node.is_leaf = True
155+
156+
return root

0 commit comments

Comments
 (0)