Skip to content

Commit 919500c

Browse files
authored
Merge pull request #1387 from seungriyou/main
2 parents e871eaf + c1cb3e1 commit 919500c

File tree

5 files changed

+296
-0
lines changed

5 files changed

+296
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# https://leetcode.com/problems/best-time-to-buy-and-sell-stock/
2+
3+
from typing import List
4+
5+
class Solution:
6+
def maxProfit1(self, prices: List[int]) -> int:
7+
"""
8+
[Complexity]
9+
- TC: O(n)
10+
- SC: O(1)
11+
12+
[Approach]
13+
prices를 순회하면서 다음을 트래킹하면 된다.
14+
(1) 지금까지의 최소 가격인 min_price 트래킹: min(min_price, price)
15+
(2) 지금까지의 최대 수익인 max_profit 트래킹: max(max_profit, price - min_price)
16+
"""
17+
max_profit, min_price = 0, prices[0]
18+
19+
for price in prices:
20+
min_price = min(min_price, price)
21+
max_profit = max(max_profit, price - min_price)
22+
23+
return max_profit
24+
25+
def maxProfit(self, prices: List[int]) -> int:
26+
"""
27+
[Complexity]
28+
- TC: O(n)
29+
- SC: O(1)
30+
31+
[Approach]
32+
two-pointer(buy, sell)로도 접근 가능하다.
33+
두 개의 pointer buy와 sell을 0, 1 인덱스에서 시작한 후, sell을 움직여가며 curr_profit = prices[sell] - prices[buy]를 구한다.
34+
그리고 curr_profit이 0보다 크면 max_profit을 트래킹하고, 그렇지 않으면 buy를 sell 위치로 당긴다.
35+
(curr_profit이 0보다 크지 않다는 것은, buy 시점의 price 이하인 price가 sell 시점에서 발견되었다는 뜻이다!)
36+
"""
37+
buy, sell = 0, 1
38+
max_profit = 0
39+
40+
while sell < len(prices):
41+
curr_profit = prices[sell] - prices[buy]
42+
if curr_profit > 0:
43+
max_profit = max(max_profit, curr_profit)
44+
else:
45+
buy = sell
46+
sell += 1
47+
48+
return max_profit
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# https://leetcode.com/problems/encode-and-decode-strings/
2+
3+
from typing import List
4+
5+
class Codec:
6+
def encode(self, strs: List[str]) -> str:
7+
"""Encodes a list of strings to a single string.
8+
9+
[Approach]
10+
(length + delimiter + string)의 형태로 str를 구성하면 length 만큼 포인터를 건너뛰며 확인할 수 있게 된다.
11+
delimiter는 숫자(length) 바로 뒤에 처음으로 나오는 문자여야 하므로, 숫자가 아닌 값으로 해야 한다. (나는 "."으로)
12+
"""
13+
# length + delimiter + string
14+
return f"".join(str(len(s)) + "." + s for s in strs)
15+
16+
def decode(self, s: str) -> List[str]:
17+
"""Decodes a single string to a list of strings.
18+
"""
19+
strs = []
20+
21+
start = 0
22+
while start < len(s):
23+
# 1. start 이후에 나오는 첫 delimiter 위치 찾기
24+
# for delim in range(start, len(s)):
25+
# if s[delim] == ".":
26+
# break
27+
delim = s.find(".", start)
28+
29+
# 2. 보고 있는 str의 length 구하기
30+
length = int(s[start:delim])
31+
32+
# 3. 보고 있는 str의 다음 위치 구하기
33+
end = delim + 1 + length
34+
35+
# 4. 현재 str 모으기
36+
strs.append(s[delim + 1:end])
37+
38+
# 5. start를 end로 이동
39+
start = end
40+
41+
return strs
42+
43+
# Your Codec object will be instantiated and called as such:
44+
# codec = Codec()
45+
# codec.decode(codec.encode(strs))

group-anagrams/seungriyou.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# https://leetcode.com/problems/group-anagrams/
2+
3+
from typing import List
4+
5+
class Solution:
6+
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
7+
"""
8+
[Approach]
9+
- TC: O(n * klogk) (n = len(strs), k = max length of an element)
10+
- SC: O(n * k)
11+
12+
[Complexity]
13+
(1) strs의 원소들을 정렬해서 (2) hash table의 key 값으로 사용하면
14+
매 단계마다 O(1)에 anagram을 찾을 수 있다.
15+
이때, key 값으로는 list는 사용할 수 없고, string이나 tuple을 사용해야 한다.
16+
"""
17+
from collections import defaultdict
18+
19+
anagrams = defaultdict(list)
20+
21+
for s in strs:
22+
anagrams["".join(sorted(s))].append(s)
23+
24+
return list(anagrams.values())
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# https://leetcode.com/problems/implement-trie-prefix-tree/
2+
3+
from collections import defaultdict
4+
5+
6+
class TrieNode:
7+
def __init__(self):
8+
self.is_word = False
9+
self.children = defaultdict(TrieNode)
10+
11+
12+
class Trie:
13+
def __init__(self):
14+
self.root = TrieNode()
15+
16+
def insert(self, word: str) -> None:
17+
curr = self.root
18+
19+
for w in word:
20+
curr = curr.children[w]
21+
22+
curr.is_word = True
23+
24+
def search(self, word: str) -> bool:
25+
curr = self.root
26+
27+
for w in word:
28+
if w not in curr.children:
29+
return False
30+
curr = curr.children[w]
31+
32+
return curr.is_word
33+
34+
def startsWith(self, prefix: str) -> bool:
35+
curr = self.root
36+
37+
for p in prefix:
38+
if p not in curr.children:
39+
return False
40+
curr = curr.children[p]
41+
42+
return True
43+
44+
# Your Trie object will be instantiated and called as such:
45+
# obj = Trie()
46+
# obj.insert(word)
47+
# param_2 = obj.search(word)
48+
# param_3 = obj.startsWith(prefix)

word-break/seungriyou.py

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# https://leetcode.com/problems/word-break/
2+
3+
from typing import List
4+
5+
class Solution:
6+
def wordBreak_bfs(self, s: str, wordDict: List[str]) -> bool:
7+
"""
8+
[Complexity]
9+
- TC: O(n^3 + m * k) (n = len(s), m = len(wordDict), k = avg(len(word) for word in wordDict))
10+
- O(n^3): (1) s[start:end] slicing * (2) end * (3) start
11+
- O(m * k): set(wordDict)
12+
- SC: O(n + m * k)
13+
- O(n): seen, q
14+
- O(m * k): set(wordDict)
15+
16+
[Approach]
17+
BFS로 접근할 수 있다.
18+
이때, queue는 확인할 substring의 시작 index를 기록하면 되며, seen을 이용하여 각 start 당 한 번씩만 확인한다.
19+
"""
20+
from collections import deque
21+
22+
n, words = len(s), set(wordDict)
23+
24+
q = deque([0]) # 확인할 substring의 시작 index
25+
seen = set() # 중복 X
26+
27+
while q:
28+
start = q.popleft()
29+
30+
# base condition
31+
if start == n:
32+
return True
33+
34+
# string slicing에서 사용할 끝 index를 순회하며 확인
35+
for end in range(start + 1, n + 1):
36+
# (1) 방문하지 않았으며 (2) wordDict에 현재 보고 있는 substring이 있는 경우
37+
if end not in seen and s[start:end] in words:
38+
q.append(end)
39+
seen.add(end)
40+
41+
return False
42+
43+
def wordBreak_dp(self, s: str, wordDict: List[str]) -> bool:
44+
"""
45+
[Complexity]
46+
- TC: O(n^3 + m * k)
47+
- O(n^3): (1) s[start:end] slicing * (2) end * (3) start
48+
- O(m * k): set(wordDict)
49+
- SC: O(n + m * k)
50+
- O(n): dp
51+
- O(m * k): set(wordDict)
52+
53+
[Approach]
54+
DP로 접근할 수 있다.
55+
이때, dp[i] = s[i:]가 wordDict의 원소만으로 구성될 수 있는지 여부이다.
56+
따라서 핵심은 뒤에서부터 확인하는 것이다!
57+
"""
58+
59+
n, words = len(s), set(wordDict)
60+
61+
# dp[i] = s[i:]가 wordDict의 원소만으로 구성될 수 있는지 여부
62+
dp = [False] * (n + 1)
63+
dp[n] = True
64+
65+
for start in range(n - 1, -1, -1):
66+
for end in range(start + 1, n + 1):
67+
# (1) s[end:]가 wordDict 원소로 구성 가능하고 (2) wordDict에 현재 보고 있는 substring이 있는 경우
68+
if dp[end] and s[start:end] in words:
69+
dp[start] = True
70+
break
71+
72+
return dp[0]
73+
74+
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
75+
"""
76+
[Complexity]
77+
- TC: O(n^2 + m * k)
78+
- O(n^2): 루프 수행 (slicing 제거)
79+
- O(m * k): trie 생성
80+
- SC: O(n + m * k)
81+
- O(n): dp list
82+
- O(m * k): trie
83+
84+
[Approach]
85+
trie를 이용하면, dp에서 매 루프마다 s[start:end] slicing으로 O(n)이 소요되던 것을 O(1)로 최적화 할 수 있다.
86+
"""
87+
from collections import defaultdict
88+
89+
class TrieNode:
90+
def __init__(self):
91+
self.is_word = False
92+
self.children = defaultdict(TrieNode)
93+
94+
def add_word(self, word):
95+
curr = self
96+
97+
for w in word:
98+
curr = curr.children[w]
99+
100+
curr.is_word = True
101+
102+
# 1. trie 구성하기
103+
root = TrieNode()
104+
for word in wordDict:
105+
root.add_word(word)
106+
107+
# 2. dp 준비하기
108+
n, words = len(s), set(wordDict)
109+
# dp[i] = s[i:]가 wordDict의 원소만으로 구성될 수 있는지 여부
110+
dp = [False] * (n + 1)
111+
dp[n] = True
112+
113+
# 3. trie를 이용하여 최적화된 dp 수행
114+
for start in range(n - 1, -1, -1):
115+
curr = root
116+
for end in range(start + 1, n + 1): # -- trie 사용으로 각 단계 O(n) -> O(1)
117+
w = s[end - 1]
118+
119+
# 현재 보고 있는 substring의 마지막 문자가 curr.children에 없다면 중단
120+
if w not in curr.children:
121+
break
122+
123+
# 있다면 타고 내려가기
124+
curr = curr.children[w]
125+
126+
# (1) s[end:]가 wordDict 원소로 구성 가능하고 (2) wordDict의 word가 발견되었다면 dp 리스트에 기록 후 중단
127+
if dp[end] and curr.is_word:
128+
dp[start] = True
129+
break
130+
131+
return dp[0]

0 commit comments

Comments
 (0)