Skip to content

Commit 6411e48

Browse files
authored
Merge pull request #456 from lymchgmk/feat/week5
[EGON] Week 5 Solutions
2 parents 71bc447 + 15d5be9 commit 6411e48

File tree

5 files changed

+382
-0
lines changed

5 files changed

+382
-0
lines changed

3sum/EGON.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from typing import List
2+
from unittest import TestCase, main
3+
4+
5+
class Solution:
6+
def threeSum(self, nums: List[int]) -> List[List[int]]:
7+
return self.solveWithTwoPointer(nums)
8+
9+
"""
10+
Runtime: 691 ms (Beats 62.42%)
11+
Time Complexity: O(n^2)
12+
- nums를 정렬하는데 O(n * log n)
13+
- 첫 index를 정하기 위해 range(len(nums) - 2) 조회하는데 O(n - 2)
14+
- i + 1 부터 n - 1까지 lo, hi 투포인터 조회하는데, i가 최소값인 0인 경우를 upper bound로 계산하면 O(n - 1)
15+
> O(n * log n) + O(n - 2) * O(n - 1) ~= O(n * log n) + O(n^2) ~= O(n^2)
16+
17+
Memory: 20.71 MB (Beats 30.94%)
18+
Space Complexity:
19+
- num는 정렬하긴 했는데 자기자신 그대로 사용하므로 계산 외
20+
> lo나 hi나 triplet_sum은 input에 영향없는 크기의 메모리를 사용하므로 O(1)
21+
"""
22+
23+
def solveWithTwoPointer(self, nums: List[int]) -> List[List[int]]:
24+
nums.sort()
25+
triplets = []
26+
27+
for i in range(len(nums) - 2):
28+
if 1 <= i and nums[i] == nums[i - 1]:
29+
continue
30+
31+
lo, hi = i + 1, len(nums) - 1
32+
while lo < hi:
33+
triplet_sum = nums[i] + nums[lo] + nums[hi]
34+
35+
if triplet_sum < 0:
36+
lo += 1
37+
elif triplet_sum > 0:
38+
hi -= 1
39+
else:
40+
triplets.append([nums[i], nums[lo], nums[hi]])
41+
42+
while lo < hi and nums[lo] == nums[lo + 1]:
43+
lo += 1
44+
while lo < hi and nums[hi] == nums[hi - 1]:
45+
hi -= 1
46+
47+
lo += 1
48+
hi -= 1
49+
50+
return triplets
51+
52+
53+
class _LeetCodeTestCases(TestCase):
54+
def test_1(self):
55+
nums = [-1, 0, 1, 2, -1, -4]
56+
output = [[-1, -1, 2], [-1, 0, 1]]
57+
self.assertEqual(Solution.threeSum(Solution(), nums), output)
58+
59+
def test_2(self):
60+
nums = [0, 1, 1]
61+
output = []
62+
self.assertEqual(Solution.threeSum(Solution(), nums), output)
63+
64+
def test_3(self):
65+
strs = [0, 0, 0]
66+
output = [[0, 0, 0]]
67+
self.assertEqual(Solution.threeSum(Solution(), strs), output)
68+
69+
def test_4(self):
70+
strs = [0, 0, 0, 0, 0, 0, 0, 0]
71+
output = [[0, 0, 0]]
72+
self.assertEqual(Solution.threeSum(Solution(), strs), output)
73+
74+
75+
if __name__ == '__main__':
76+
main()
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from typing import List
2+
from unittest import TestCase, main
3+
4+
5+
class Solution:
6+
def maxProfit(self, prices: List[int]) -> int:
7+
return self.solveWithStack(prices)
8+
9+
"""
10+
Runtime: 749 ms (Beats 46.22%)
11+
Time Complexity: O(n)
12+
- prices의 길이 n 만큼 조회에 O(n)
13+
- 조회하며 수행하는 연산들은 .pop, .append, 고정된 크기에 대한 max 이므로 O(1)
14+
> O(n) * O(1) ~= O(n)
15+
16+
Memory: 27.33 MB (Beats 91.14%)
17+
Space Complexity: O(1)
18+
- 크기가 1로 유지되는 stack: List[int] 사용
19+
- max_profit: int 사용
20+
> O(1)
21+
"""
22+
def solveWithStack(self, prices: List[int]) -> int:
23+
max_profit = 0
24+
stack = []
25+
for price in prices:
26+
if not stack:
27+
stack.append(price)
28+
continue
29+
30+
min_price = stack.pop()
31+
if min_price < price:
32+
max_profit = max(price - min_price, max_profit)
33+
stack.append(min_price)
34+
elif min_price > price:
35+
stack.append(price)
36+
else:
37+
stack.append(min_price)
38+
39+
return max_profit
40+
41+
42+
class _LeetCodeTestCases(TestCase):
43+
def test_1(self):
44+
prices = [7,1,5,3,6,4]
45+
output = 5
46+
self.assertEqual(Solution.maxProfit(Solution(), prices), output)
47+
48+
def test_2(self):
49+
prices = [7,6,4,3,1]
50+
output = 0
51+
self.assertEqual(Solution.maxProfit(Solution(), prices), output)
52+
53+
54+
if __name__ == '__main__':
55+
main()

group-anagrams/EGON.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from typing import List
2+
from unittest import TestCase, main
3+
4+
5+
class Solution:
6+
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
7+
return self.solveWithHashableKey(strs)
8+
9+
"""
10+
Runtime: 88 ms (Beats 65.68%)
11+
Time Complexity:
12+
- strs의 길이 n 만큼 조회에 O(n)
13+
- string을 key로 변환하는데 string의 길이 L에 대해 O(L * log L) * O(L)
14+
- string의 최대 길이는 100이므로 O(100 * log 100) * O(100)가 upper bound
15+
- anagram_dict 갱신에 O(1)
16+
- 최대 크기가 n인 anagram_dict.values()를 list로 변환하는데 O(n^2)
17+
> O(n) * O(L * log L) * O(L) + O(n^2) ~= O(n^2)
18+
19+
Memory: 19.32 MB (Beats 95.12%)
20+
Space Complexity: O(n * L)
21+
- 최대 n개의 key-value pair를 가질 수 있는 anagram_dict 사용, upper bound
22+
anagram_dict의 value는 최대 길이 L인 List[str] 이므로 O(n * L)
23+
> O(n * L)
24+
"""
25+
def solveWithHashableKey(self, strs: List[str]) -> List[List[str]]:
26+
anagram_dict = {}
27+
for string in strs:
28+
key = ''.join(sorted(string))
29+
anagram_dict[key] = anagram_dict.get(key, []) + [string]
30+
31+
return list(anagram_dict.values())
32+
33+
34+
class _LeetCodeTestCases(TestCase):
35+
def test_1(self):
36+
strs = ["eat","tea","tan","ate","nat","bat"]
37+
output = [["bat"],["nat","tan"],["ate","eat","tea"]]
38+
self.assertEqual(Solution.groupAnagrams(Solution(), strs), output)
39+
40+
def test_2(self):
41+
strs = [""]
42+
output = [[""]]
43+
self.assertEqual(Solution.groupAnagrams(Solution(), strs), output)
44+
45+
def test_3(self):
46+
strs = ["a"]
47+
output = [["a"]]
48+
self.assertEqual(Solution.groupAnagrams(Solution(), strs), output)
49+
50+
51+
if __name__ == '__main__':
52+
main()

implement-trie-prefix-tree/EGON.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from unittest import TestCase, main
2+
3+
4+
"""
5+
Runtime: 136 ms (Beats 33.86%)
6+
Time Complexity:
7+
> insert: word의 각 문자마다 조회하므로 O(L)
8+
> search: 최대 word의 모든 문자를 조회하므로 O(L), upper bound
9+
> startsWith: 최대 prefix의 모든 문자를 조회하므로 O(L'), upper bound
10+
11+
Memory: 32.60 MB (Beats 16.08%)
12+
Space Complexity: O(n * L), upper bound
13+
- 최악의 경우 삽입하는 모든 word가 공통점이 하나도 없는 경우를 상정해보면, 삽입하는 word의 갯수를 n, word의 최대 길이를 L
14+
> O(n * L), upper bound
15+
"""
16+
17+
18+
class Node:
19+
def __init__(self, key, data=None):
20+
self.key = key
21+
self.data = data
22+
self.children = {}
23+
24+
25+
class Trie:
26+
27+
def __init__(self):
28+
self.root = Node(None)
29+
30+
def insert(self, word: str) -> None:
31+
curr_node = self.root
32+
for char in word:
33+
if char not in curr_node.children:
34+
curr_node.children[char] = Node(char)
35+
36+
curr_node = curr_node.children[char]
37+
38+
curr_node.data = word
39+
40+
def search(self, word: str) -> bool:
41+
curr_node = self.root
42+
for char in word:
43+
if char in curr_node.children:
44+
curr_node = curr_node.children[char]
45+
else:
46+
return False
47+
48+
return curr_node.data == word
49+
50+
def startsWith(self, prefix: str) -> bool:
51+
curr_node = self.root
52+
for char in prefix:
53+
if char in curr_node.children:
54+
curr_node = curr_node.children[char]
55+
else:
56+
return False
57+
else:
58+
return True
59+
60+
61+
class _LeetCodeTestCases(TestCase):
62+
def test_1(self):
63+
# commands = ["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
64+
# words = [[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
65+
# output = [None, None, True, False, True, None, True]
66+
67+
trie = Trie()
68+
trie.insert("apple")
69+
self.assertEqual(trie.search("apple"), True)
70+
self.assertEqual(trie.search("app"), False)
71+
self.assertEqual(trie.startsWith("app"), True)
72+
trie.insert("app")
73+
self.assertEqual(trie.search("app"), True)
74+
75+
76+
if __name__ == '__main__':
77+
main()

word-break/EGON.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
from typing import List
2+
from unittest import TestCase, main
3+
4+
5+
class Node:
6+
def __init__(self, key, data=None):
7+
self.key = key
8+
self.data = data
9+
self.children = {}
10+
11+
12+
class Trie:
13+
14+
def __init__(self):
15+
self.root = Node(None)
16+
17+
def insert(self, word: str) -> None:
18+
curr_node = self.root
19+
for char in word:
20+
if char not in curr_node.children:
21+
curr_node.children[char] = Node(char)
22+
23+
curr_node = curr_node.children[char]
24+
25+
curr_node.data = word
26+
27+
def search(self, word: str) -> bool:
28+
curr_node = self.root
29+
for char in word:
30+
if char in curr_node.children:
31+
curr_node = curr_node.children[char]
32+
else:
33+
return False
34+
35+
return curr_node.data == word
36+
37+
def startsWith(self, prefix: str) -> bool:
38+
curr_node = self.root
39+
for char in prefix:
40+
if char in curr_node.children:
41+
curr_node = curr_node.children[char]
42+
else:
43+
return False
44+
else:
45+
return True
46+
47+
48+
class Solution:
49+
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
50+
return self.solveWithTrieDFSMemo(s, wordDict)
51+
52+
"""
53+
Runtime: 38 ms (Beats 70.76%)
54+
Time Complexity: O(n * L + s^2)
55+
- s의 길이를 S, wordDict의 길이를 L, word의 최대 길이를 W라 하면
56+
- trie에 insert하는데 O(W * L), upper bound
57+
- DFS에서 curr_s의 모든 문자 조회에 최대 O(S), 접두사를 제거하는 curr_s.removeprefix에서 최대 O(S)
58+
> O(W * L) + O(S) * O(S) = O(W * L + S^2) upper bound
59+
60+
Memory: 17.70 MB (Beats 5.25%)
61+
Space Complexity:
62+
- trie 생성 및 갱신에 O(W * L)
63+
- visited는 최악의 경우 s의 모든 부분 문자열을 저장하므로 O(S^2) upper bound
64+
> O(W * L) + O(S^2) = O(W * L + S^2) upper bound
65+
"""
66+
def solveWithTrieDFSMemo(self, s: str, wordDict: List[str]) -> bool:
67+
trie = Trie()
68+
for word in wordDict:
69+
trie.insert(word)
70+
71+
stack = [s]
72+
visited = set()
73+
while stack:
74+
curr_s = stack.pop()
75+
if curr_s in visited:
76+
continue
77+
78+
curr_node = trie.root
79+
for char in curr_s:
80+
if char in curr_node.children:
81+
curr_node = curr_node.children[char]
82+
if curr_node.data is not None:
83+
post_s = curr_s.removeprefix(curr_node.data)
84+
if not post_s:
85+
return True
86+
else:
87+
stack.append(post_s)
88+
else:
89+
visited.add(curr_s)
90+
break
91+
92+
return False
93+
94+
95+
class _LeetCodeTestCases(TestCase):
96+
def test_1(self):
97+
s = "leetcode"
98+
wordDict = ["leet","code"]
99+
output = True
100+
self.assertEqual(Solution.wordBreak(Solution(), s, wordDict), output)
101+
102+
def test_2(self):
103+
s = "applepenapple"
104+
wordDict = ["apple","pen"]
105+
output = True
106+
self.assertEqual(Solution.wordBreak(Solution(), s, wordDict), output)
107+
108+
def test_3(self):
109+
s = "catsandog"
110+
wordDict = ["cats","dog","sand","and","cat"]
111+
output = False
112+
self.assertEqual(Solution.wordBreak(Solution(), s, wordDict), output)
113+
114+
def test_4(self):
115+
s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"
116+
wordDict = ["a","aa","aaa","aaaa","aaaaa","aaaaaa","aaaaaaa","aaaaaaaa","aaaaaaaaa","aaaaaaaaaa"]
117+
output = False
118+
self.assertEqual(Solution.wordBreak(Solution(), s, wordDict), output)
119+
120+
121+
if __name__ == '__main__':
122+
main()

0 commit comments

Comments
 (0)