Skip to content

Commit aeffd8e

Browse files
committed
[alphaorderly] WEEK 05 Solutions
1 parent 9b11b3c commit aeffd8e

5 files changed

Lines changed: 232 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

group-anagrams/alphaorderly.py

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: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""
2+
Time Complexity: O(n * k * m)
3+
- n = len(s)
4+
- k = number of words in wordDict
5+
- m = average word length in wordDict
6+
7+
At each index in s, we may check every word in wordDict and for each, compare up to m characters.
8+
9+
Space Complexity: O(n)
10+
- n = len(s), due to recursion stack and memoization table (one entry per possible starting index).
11+
12+
- Uses top-down dynamic programming with memoization (via lru_cache).
13+
- The helper function dp(index) returns True if s[index:] can be fully segmented into words from wordDict.
14+
- Base case: If index == len(s), then the entire string is successfully segmented.
15+
- For each call, iterate through all words in the dictionary and check if s[index:] begins with that word.
16+
- If a match is found, recursively check for the remainder of the string starting after the matched word.
17+
- Return True as soon as any valid segmentation is found.
18+
- Return False if no segmentation is possible for this index.
19+
"""
20+
class Solution:
21+
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
22+
@lru_cache()
23+
def dp(index: int) -> bool:
24+
if index == len(s):
25+
return True
26+
27+
for word in wordDict:
28+
if len(s) - index < len(word):
29+
continue
30+
31+
if s[index:].startswith(word) and dp(index + len(word)):
32+
return True
33+
34+
return False
35+
36+
return dp(0)
37+
38+
"""
39+
Time Complexity: O(n * k * m)
40+
- n = len(s)
41+
- k = number of words in wordDict
42+
- m = average word length in wordDict
43+
44+
For every index in s, we consider each word in wordDict and, for each, match up to m characters.
45+
46+
Space Complexity: O(n)
47+
- n = len(s), required for the dp array.
48+
49+
- Uses bottom-up dynamic programming.
50+
- The dp array indicates whether a prefix of s up to index can be segmented into words from wordDict.
51+
- Base case: dp[0] = 1, since the empty string can always be segmented.
52+
- For each index, iterate through all words, and for each, check if s[index - W : index] equals the word, assuming index >= W and dp[index - W] is True.
53+
- If a match is found, update dp[index] to reflect that segmentation.
54+
- Finally, return True if dp[-1] is set, indicating the entire string can be segmented; otherwise, return False.
55+
"""
56+
class Solution:
57+
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
58+
S = len(s)
59+
60+
dp = [0] * (S + 1)
61+
dp[0] = 1
62+
63+
for index in range(S + 1):
64+
for word in wordDict:
65+
W = len(word)
66+
if index < W or dp[index - W] == 0:
67+
continue
68+
if s[index - W :].startswith(word):
69+
dp[index] = dp[index - W]
70+
71+
return bool(dp[-1])

0 commit comments

Comments
 (0)