Skip to content

Commit 70083c5

Browse files
alphaorderlyokyungjin
authored andcommitted
[alphaorderly] WEEK 05 Solutions (DaleStudy#2758)
* week 1 * [alphaorderly] WEEK 02 Solutions * fix: description์— ๋งž๊ฒŒ ์ฝ”๋“œ ์ˆ˜์ • * fix: ์ข€ ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ˆ˜์ • * [alphaorderly] WEEK 03 Solutions * fix: ๋ถˆํ•„์š”ํ•œ ์ฝ”๋“œ ์‚ญ์ œ * fix: ์ค„๋ฐ”๊ฟˆ ๋ฆฐํŠธ ๋ฌธ์ œ ์ˆ˜์ • * [alphaorderly] WEEK 03 Solutions * fix: ๋ฆฐํŠธ ์˜ค๋ฅ˜ ์ˆ˜์ • * fix: ํŒŒ์ด์ฌ ๋‚ด์žฅํ•จ์ˆ˜ ์‚ฌ์šฉ ์ฝ”๋“œ ์ถ”๊ฐ€ * fix: valid-palindrome ๋ฌธ์ œ์— ํˆฌํฌ์ธํ„ฐ ๊ตฌํ˜„ ๋‹ต์•ˆ ์ถ”๊ฐ€ * fix: ๋กœ์ง ๊ฐ€๋…์„ฑ ์ˆ˜์ • * [alphaorderly] WEEK 04 Solutions - draft * fix: ์ฝ”๋“œ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ * [alphaorderly] WEEK 05 Solutions * trie ์‚ฌ์šฉํ•œ ํ’€์ด๋ฒ• ์ถ”๊ฐ€ * fix: ์ฝ”๋“œ ์ฒดํฌ ์‹คํŒจ ๊ฐœ์„  * ์ฃผ์„ ๊ฐœ์„ 
1 parent 33afa9e commit 70083c5

5 files changed

Lines changed: 313 additions & 0 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""
2+
Time Complexity: O(n)
3+
Space Complexity: O(1)
4+
5+
- Single Pass approach
6+
- Use a variable to store the least price
7+
- Use a variable to store the maximum profit
8+
- Update the maximum profit whenever the current price is greater than the least price
9+
"""
10+
class Solution:
11+
def maxProfit(self, prices: List[int]) -> int:
12+
N = len(prices)
13+
14+
least = prices[0]
15+
ans = 0
16+
17+
for i in range(1, N):
18+
ans = max(ans, prices[i] - least)
19+
least = min(least, prices[i])
20+
21+
return ans
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""
2+
Time Complexity: O(n)
3+
Space Complexity: O(n)
4+
5+
### Wrong Approach ( NEVER USE IT )
6+
- Uses eval() on potentially untrusted input, which is a major security risk.
7+
- If s or strs can be controlled or modified externally, arbitrary code execution is possible.
8+
- This is for illustration only; never use eval for decoding in production code.
9+
10+
- Encodes by converting the list of strings to its string representation using str().
11+
- Decodes by evaluating that string with eval(), reconstructing the original list.
12+
"""
13+
class Codec:
14+
def encode(self, strs: List[str]) -> str:
15+
return str(strs)
16+
17+
def decode(self, s: str) -> List[str]:
18+
return eval(s)
19+
20+
"""
21+
Time Complexity: O(n)
22+
Space Complexity: O(n)
23+
24+
- Encodes the list of strings by prefixing each string with its length and a separator ("_"), then concatenating.
25+
- Decodes by parsing each length, extracting the separated string accordingly.
26+
- Does not use eval, so this approach is safe.
27+
"""
28+
class Codec:
29+
def encode(self, strs: List[str]) -> str:
30+
strs = [str(len(s)) + "_" + s for s in strs]
31+
return "".join(strs)
32+
33+
def decode(self, s: str) -> List[str]:
34+
ans = []
35+
N = len(s)
36+
index = 0
37+
size = 0
38+
39+
word_part = False
40+
41+
while index < N:
42+
ch = s[index]
43+
if not word_part:
44+
if ch == "_":
45+
if size == 0:
46+
ans.append("")
47+
else:
48+
word_part = True
49+
else:
50+
size = (size * 10) + int(ch)
51+
index += 1
52+
else:
53+
ans.append(s[index : index + size])
54+
index += size
55+
size = 0
56+
word_part = False
57+
58+
return ans
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""
2+
Time Complexity: O(n * k log k)
3+
Space Complexity: O(n * k)
4+
5+
- Iterate through each word in the input list
6+
- Use a dictionary to group anagrams by their sorted tuple of characters as the key
7+
- For each word, sort its characters and use the resulting tuple as the dictionary key
8+
- Append the word to the appropriate list in the dictionary
9+
- Return a list of the grouped anagrams
10+
"""
11+
class Solution:
12+
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
13+
d = defaultdict(list)
14+
15+
for word in strs:
16+
d[tuple(sorted(word))].append(word)
17+
18+
return list(d.values())
19+
20+
"""
21+
Time Complexity: O(n * k)
22+
Space Complexity: O(n * k)
23+
24+
- Iterate through each word in the input list
25+
- Use a dictionary to group anagrams; key is a tuple representing character counts
26+
- For each word, build a character frequency tuple (instead of sorting)
27+
- This tuple serves as a unique identifier for anagrams
28+
- Append the word to the appropriate list in the dictionary
29+
- Return a list of the grouped anagrams
30+
"""
31+
class Solution:
32+
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
33+
d = defaultdict(list)
34+
35+
def hash(s: str) -> Tuple[int, ...]:
36+
count = [0] * 26
37+
for ch in s:
38+
count[ord(ch) - ord('a')] += 1
39+
return tuple(count)
40+
41+
for word in strs:
42+
d[hash(word)].append(word)
43+
44+
return list(d.values())
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""
2+
Time Complexity: O(L), where L is the length of the input string (word or prefix) per operation.
3+
Space Complexity: O(N), where N is the total number of characters inserted into the trie (all words inserted).
4+
5+
- Each Trie node uses a dictionary to store children nodes for each character.
6+
- The `end` flag indicates whether the current node represents the end of a complete word.
7+
- The `insert` method adds a word to the trie by sequentially creating/following nodes for each character.
8+
- The `search` method checks for the presence of a full word in the trie by traversing nodes for each character and checking the `end` flag at the last character.
9+
- The `startsWith` method checks if there exists any word in the trie with the given prefix by traversing nodes for each character in the prefix.
10+
"""
11+
class Trie:
12+
13+
def __init__(self):
14+
self.children = dict()
15+
self.end = False
16+
17+
def insert(self, word: str) -> None:
18+
node = self
19+
20+
for ch in word:
21+
if ch not in node.children:
22+
node.children[ch] = Trie()
23+
node = node.children[ch]
24+
25+
node.end = True
26+
27+
def search(self, word: str, isPrefix: bool = False) -> bool:
28+
node = self
29+
30+
for ch in word:
31+
if ch not in node.children:
32+
return False
33+
node = node.children[ch]
34+
35+
return True if isPrefix else node.end
36+
37+
def startsWith(self, prefix: str) -> bool:
38+
return self.search(word=prefix, isPrefix=True)

โ€Žword-break/alphaorderly.pyโ€Ž

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""
2+
Top-down dynamic programming with memoization.
3+
4+
Time Complexity: O(k * n * (n + m)), or O(k * n^2) when m <= n
5+
- n = len(s)
6+
- k = len(wordDict)
7+
- m = the maximum word length
8+
9+
There are at most n memoized states, and each state checks every word.
10+
In this implementation, s[index:] also creates a suffix whose length is
11+
O(n), while startswith may compare up to m characters.
12+
13+
Space Complexity: O(n)
14+
The memoization table and recursion stack each contain at most n entries.
15+
16+
dp(index) returns whether the suffix beginning at index can be completely
17+
segmented. Reaching len(s) means that all characters have been consumed.
18+
For every dictionary word that matches the current suffix, the function
19+
recursively checks the remaining suffix and stops at the first valid split.
20+
"""
21+
class Solution:
22+
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
23+
@lru_cache()
24+
def dp(index: int) -> bool:
25+
if index == len(s):
26+
return True
27+
28+
for word in wordDict:
29+
if len(s) - index < len(word):
30+
continue
31+
32+
if s[index:].startswith(word) and dp(index + len(word)):
33+
return True
34+
35+
return False
36+
37+
return dp(0)
38+
39+
"""
40+
Bottom-up dynamic programming over prefixes of s.
41+
42+
Time Complexity: O(k * n * (n + m)), or O(k * n^2) when m <= n
43+
- n = len(s)
44+
- k = len(wordDict)
45+
- m = the maximum word length
46+
47+
Each of the n + 1 prefix boundaries checks every word. The expression
48+
s[index - W:] creates a suffix of up to O(n) characters, and startswith
49+
may compare up to m characters.
50+
51+
Space Complexity: O(n)
52+
The dp array stores one value for every prefix boundary.
53+
54+
dp[index] indicates whether s[:index] can be segmented. dp[0] is true because
55+
the empty prefix needs no words. A word of length W can end at index when the
56+
preceding prefix, dp[index - W], is valid and the suffix beginning there starts
57+
with that word. The final entry represents the entire string.
58+
"""
59+
class Solution:
60+
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
61+
S = len(s)
62+
63+
dp = [0] * (S + 1)
64+
dp[0] = 1
65+
66+
for index in range(S + 1):
67+
for word in wordDict:
68+
W = len(word)
69+
if index < W or dp[index - W] == 0:
70+
continue
71+
if s[index - W :].startswith(word):
72+
dp[index] = dp[index - W]
73+
74+
return bool(dp[-1])
75+
76+
"""
77+
Trie-assisted depth-first search over valid word boundaries.
78+
79+
Time Complexity: O(L + n^2)
80+
- n = len(s)
81+
- L = the total number of characters in wordDict
82+
83+
Building the trie takes O(L). Each reachable boundary is processed once,
84+
but a trie lookup may scan the remaining suffix, giving O(n^2) total work
85+
in the worst case.
86+
87+
Space Complexity: O(L + n)
88+
The trie uses O(L) space. The visited array, DFS stack, and a lookup's list
89+
of matching end positions use O(n) additional space.
90+
91+
startsWith(target, start) follows the trie from target[start:] and returns every
92+
exclusive end index at which a dictionary word finishes. The DFS treats those
93+
indices as the next segmentation boundaries. visited prevents the same boundary
94+
from being added to the stack more than once.
95+
"""
96+
class Trie:
97+
def __init__(self):
98+
self.children = dict()
99+
self.end = False
100+
101+
def insert(self, target: int):
102+
node = self
103+
104+
for ch in target:
105+
if ch not in node.children:
106+
node.children[ch] = Trie()
107+
node = node.children[ch]
108+
109+
node.end = True
110+
111+
def startsWith(self, target: str, start: int) -> List[int]:
112+
ans = []
113+
node = self
114+
115+
for i in range(start, len(target)):
116+
ch = target[i]
117+
118+
if ch not in node.children:
119+
return ans
120+
121+
node = node.children[ch]
122+
if node.end:
123+
ans.append(i + 1)
124+
125+
return ans
126+
127+
128+
class Solution:
129+
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
130+
S = len(s)
131+
t = Trie()
132+
visited = [False] * (S + 1)
133+
134+
for word in wordDict:
135+
t.insert(word)
136+
137+
stack = [0]
138+
139+
while stack:
140+
start = stack.pop()
141+
142+
starts_with = t.startsWith(s, start)
143+
144+
for start_index in starts_with:
145+
if start_index == S:
146+
return True
147+
if visited[start_index]:
148+
continue
149+
visited[start_index] = True
150+
stack.append(start_index)
151+
152+
return False

0 commit comments

Comments
ย (0)