-
-
Notifications
You must be signed in to change notification settings - Fork 361
[alphaorderly] WEEK 05 Solutions #2758
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4d5f428
3ac63f5
2536209
612dbbb
a7c2098
79f6f86
3855235
33ab6bc
b1de0e1
f9bf16c
de3390f
b3dc7d7
17669b6
d8b5903
a14205b
b087a32
2380e81
e51ae37
9592367
555e26d
9b11b3c
aeffd8e
9fb334a
cad3e9b
5d805dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| """ | ||
| Time Complexity: O(n) | ||
| Space Complexity: O(1) | ||
| - Single Pass approach | ||
| - Use a variable to store the least price | ||
| - Use a variable to store the maximum profit | ||
| - Update the maximum profit whenever the current price is greater than the least price | ||
| """ | ||
| class Solution: | ||
| def maxProfit(self, prices: List[int]) -> int: | ||
| N = len(prices) | ||
|
|
||
| least = prices[0] | ||
| ans = 0 | ||
|
|
||
| for i in range(1, N): | ||
| ans = max(ans, prices[i] - least) | ||
| least = min(least, prices[i]) | ||
|
|
||
| return ans |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
풀이 1:
|
| 복잡도 | |
|---|---|
| Time | O(n) |
| Space | O(n) |
피드백: 문자열 길이 정보를 이용해 구분자로 구분하므로 일반적으로 안전한 인코딩이 가능하다.
개선 제안: 현재 구현이 적절해 보입니다.
풀이 2: Codec.decode — Time: O(n) / Space: O(n)
| 복잡도 | |
|---|---|
| Time | O(n) |
| Space | O(n) |
피드백: 가독성과 안전성을 높인 인코딩 방식으로 동작한다. eval 제거가 핵심 개선이다.
개선 제안: 현재 구현이 적절해 보입니다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| """ | ||
| Time Complexity: O(n) | ||
| Space Complexity: O(n) | ||
| ### Wrong Approach ( NEVER USE IT ) | ||
| - Uses eval() on potentially untrusted input, which is a major security risk. | ||
| - If s or strs can be controlled or modified externally, arbitrary code execution is possible. | ||
| - This is for illustration only; never use eval for decoding in production code. | ||
| - Encodes by converting the list of strings to its string representation using str(). | ||
| - Decodes by evaluating that string with eval(), reconstructing the original list. | ||
| """ | ||
| class Codec: | ||
| def encode(self, strs: List[str]) -> str: | ||
| return str(strs) | ||
|
|
||
| def decode(self, s: str) -> List[str]: | ||
| return eval(s) | ||
|
|
||
| """ | ||
| Time Complexity: O(n) | ||
| Space Complexity: O(n) | ||
| - Encodes the list of strings by prefixing each string with its length and a separator ("_"), then concatenating. | ||
| - Decodes by parsing each length, extracting the separated string accordingly. | ||
| - Does not use eval, so this approach is safe. | ||
| """ | ||
| class Codec: | ||
| def encode(self, strs: List[str]) -> str: | ||
| strs = [str(len(s)) + "_" + s for s in strs] | ||
| return "".join(strs) | ||
|
|
||
|
Comment on lines
+29
to
+32
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 각 문자열 길이가 0~200 이라 고정 길이 방식도 고려해볼 수 있을 것 같습니다. 데이터 패턴에 따라 1글자가 많으면 작성해주신 방식이 유리할 거 같고 3글자가 많으면 고정길이가 유리해서 trade-off는 있을 거 같아요.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 맞아요, 고정길이 코드 하신거 보니까 훨씬 깔끔하긴 하더라구요!! |
||
| def decode(self, s: str) -> List[str]: | ||
| ans = [] | ||
| N = len(s) | ||
| index = 0 | ||
| size = 0 | ||
|
|
||
| word_part = False | ||
|
|
||
| while index < N: | ||
| ch = s[index] | ||
| if not word_part: | ||
| if ch == "_": | ||
| if size == 0: | ||
| ans.append("") | ||
| else: | ||
| word_part = True | ||
| else: | ||
| size = (size * 10) + int(ch) | ||
| index += 1 | ||
| else: | ||
| ans.append(s[index : index + size]) | ||
| index += size | ||
| size = 0 | ||
| word_part = False | ||
|
|
||
| return ans | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 두 구현 모두 애너그램을 그룹화하는 일반적인 방법이다. 카운트 벡터를 사용하는 방식은 정렬보다 일정하게 성능을 보인다. 개선 제안: 현재 구현이 적절해 보입니다.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| """ | ||
| Time Complexity: O(n * k log k) | ||
| Space Complexity: O(n * k) | ||
|
|
||
| - Iterate through each word in the input list | ||
| - Use a dictionary to group anagrams by their sorted tuple of characters as the key | ||
| - For each word, sort its characters and use the resulting tuple as the dictionary key | ||
| - Append the word to the appropriate list in the dictionary | ||
| - Return a list of the grouped anagrams | ||
| """ | ||
| class Solution: | ||
| def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||
| d = defaultdict(list) | ||
|
|
||
| for word in strs: | ||
| d[tuple(sorted(word))].append(word) | ||
|
|
||
| return list(d.values()) | ||
|
|
||
| """ | ||
| Time Complexity: O(n * k) | ||
| Space Complexity: O(n * k) | ||
|
|
||
| - Iterate through each word in the input list | ||
| - Use a dictionary to group anagrams; key is a tuple representing character counts | ||
| - For each word, build a character frequency tuple (instead of sorting) | ||
| - This tuple serves as a unique identifier for anagrams | ||
| - Append the word to the appropriate list in the dictionary | ||
| - Return a list of the grouped anagrams | ||
| """ | ||
| class Solution: | ||
| def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||
| d = defaultdict(list) | ||
|
|
||
| def hash(s: str) -> Tuple[int, ...]: | ||
| count = [0] * 26 | ||
| for ch in s: | ||
| count[ord(ch) - ord('a')] += 1 | ||
| return tuple(count) | ||
|
|
||
| for word in strs: | ||
| d[hash(word)].append(word) | ||
|
|
||
| return list(d.values()) |
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trie를 압축을 할수 있을거 같은데 좀 연구해볼 필요가 있겠네요
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 트라이를 사용해 글자 단위로 탐색하는 표준 접근이다. 각 노드는 자식 맵을 가지며 종료 플래그로 단어를 표시한다. 개선 제안: 현재 구현이 적절해 보입니다.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| """ | ||
| Time Complexity: O(L), where L is the length of the input string (word or prefix) per operation. | ||
| Space Complexity: O(N), where N is the total number of characters inserted into the trie (all words inserted). | ||
|
|
||
| - Each Trie node uses a dictionary to store children nodes for each character. | ||
| - The `end` flag indicates whether the current node represents the end of a complete word. | ||
| - The `insert` method adds a word to the trie by sequentially creating/following nodes for each character. | ||
| - 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. | ||
| - 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. | ||
| """ | ||
| class Trie: | ||
|
|
||
| def __init__(self): | ||
| self.children = dict() | ||
| self.end = False | ||
|
|
||
| def insert(self, word: str) -> None: | ||
| node = self | ||
|
|
||
| for ch in word: | ||
| if ch not in node.children: | ||
| node.children[ch] = Trie() | ||
| node = node.children[ch] | ||
|
|
||
| node.end = True | ||
|
|
||
| def search(self, word: str, isPrefix: bool = False) -> bool: | ||
| node = self | ||
|
|
||
| for ch in word: | ||
| if ch not in node.children: | ||
| return False | ||
| node = node.children[ch] | ||
|
|
||
| return True if isPrefix else node.end | ||
|
|
||
| def startsWith(self, prefix: str) -> bool: | ||
| return self.search(word=prefix, isPrefix=True) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이런 방법이...! |
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
풀이 1:
|
| 복잡도 | |
|---|---|
| Time | O(k * n^2) |
| Space | O(n) |
피드백: 메모이제이션으로 중복 탐색을 피하지만 worst-case 탐색은 여전히 비싸다. startsWith 호출은 비효율적일 수 있다.
개선 제안: 고려해볼 만한 대안: 단어 사전을 트라이에 넣고 시작 지점에서 가능한 끝 지점을 빠르게 찾는 방법으로 최악의 경우를 줄일 수 있다.
풀이 2: Solution.wordBreak (bottom-up DP) — Time: O(k * n^2) / Space: O(n)
| 복잡도 | |
|---|---|
| Time | O(k * n^2) |
| Space | O(n) |
피드백: 직관적인 DP 풀이로 구현이 명확하다. 다만 inner 루프를 최적화하면 성능이 개선될 여지가 있다.
개선 제안: 현재 구현이 적절해 보입니다.
풀이 3: Trie-based DFS wordBreak — Time: O(L + n^2) / Space: O(L + n)
| 복잡도 | |
|---|---|
| Time | O(L + n^2) |
| Space | O(L + n) |
피드백: 트라이를 활용해 가능한 분할 경로를 효율적으로 탐색한다. 최악의 경우 여전히 n^2의 탐색이 발생할 수 있다.
개선 제안: 현재 구현이 적절해 보입니다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| """ | ||
| Top-down dynamic programming with memoization. | ||
|
|
||
| Time Complexity: O(k * n * (n + m)), or O(k * n^2) when m <= n | ||
| - n = len(s) | ||
| - k = len(wordDict) | ||
| - m = the maximum word length | ||
|
|
||
| There are at most n memoized states, and each state checks every word. | ||
| In this implementation, s[index:] also creates a suffix whose length is | ||
| O(n), while startswith may compare up to m characters. | ||
|
|
||
| Space Complexity: O(n) | ||
| The memoization table and recursion stack each contain at most n entries. | ||
|
|
||
| dp(index) returns whether the suffix beginning at index can be completely | ||
| segmented. Reaching len(s) means that all characters have been consumed. | ||
| For every dictionary word that matches the current suffix, the function | ||
| recursively checks the remaining suffix and stops at the first valid split. | ||
| """ | ||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
| @lru_cache() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lru_cache의 maxSize 기본값이 128인데 이것도 고려하셨을까요?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넹 맞아요, 문자열의 길이상 괜찮을것 같다는 판단이였어요
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@alphaorderly 128개 외에 밀려나는 캐시는 재계산하면 된다로 해석하면 될까요? 많이 배워갑니다👍 |
||
| def dp(index: int) -> bool: | ||
| if index == len(s): | ||
| return True | ||
|
|
||
| for word in wordDict: | ||
| if len(s) - index < len(word): | ||
| continue | ||
|
|
||
| if s[index:].startswith(word) and dp(index + len(word)): | ||
| return True | ||
|
|
||
| return False | ||
|
|
||
| return dp(0) | ||
|
|
||
| """ | ||
| Bottom-up dynamic programming over prefixes of s. | ||
|
|
||
| Time Complexity: O(k * n * (n + m)), or O(k * n^2) when m <= n | ||
| - n = len(s) | ||
| - k = len(wordDict) | ||
| - m = the maximum word length | ||
|
|
||
| Each of the n + 1 prefix boundaries checks every word. The expression | ||
| s[index - W:] creates a suffix of up to O(n) characters, and startswith | ||
| may compare up to m characters. | ||
|
|
||
| Space Complexity: O(n) | ||
| The dp array stores one value for every prefix boundary. | ||
|
|
||
| dp[index] indicates whether s[:index] can be segmented. dp[0] is true because | ||
| the empty prefix needs no words. A word of length W can end at index when the | ||
| preceding prefix, dp[index - W], is valid and the suffix beginning there starts | ||
| with that word. The final entry represents the entire string. | ||
| """ | ||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
| S = len(s) | ||
|
|
||
| dp = [0] * (S + 1) | ||
| dp[0] = 1 | ||
|
|
||
| for index in range(S + 1): | ||
| for word in wordDict: | ||
| W = len(word) | ||
|
Comment on lines
+61
to
+68
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. S, W 변수는 의미가 잘 안 드러나고 PEP 8 컨벤션이랑 달라서 가독성을 약간 떨어지게 하는 거 같습니다. PEP 8 컨벤션에 맞추어 lowercase 를 사용하시는건 어떨까요? https://peps.python.org/pep-0008/#function-and-variable-names
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하! 저는 neetcodeIO 를 예전에 엄청 오래 보면서 공부를 했었어서 배열의 길이를 상수 취급해서 대문자로 적는거에 너무 익숙해져서 그렇긴 하네요 |
||
| if index < W or dp[index - W] == 0: | ||
| continue | ||
| if s[index - W :].startswith(word): | ||
| dp[index] = dp[index - W] | ||
|
|
||
| return bool(dp[-1]) | ||
|
|
||
| """ | ||
| Trie-assisted depth-first search over valid word boundaries. | ||
|
|
||
| Time Complexity: O(L + n^2) | ||
| - n = len(s) | ||
| - L = the total number of characters in wordDict | ||
|
|
||
| Building the trie takes O(L). Each reachable boundary is processed once, | ||
| but a trie lookup may scan the remaining suffix, giving O(n^2) total work | ||
| in the worst case. | ||
|
|
||
| Space Complexity: O(L + n) | ||
| The trie uses O(L) space. The visited array, DFS stack, and a lookup's list | ||
| of matching end positions use O(n) additional space. | ||
|
|
||
| startsWith(target, start) follows the trie from target[start:] and returns every | ||
| exclusive end index at which a dictionary word finishes. The DFS treats those | ||
| indices as the next segmentation boundaries. visited prevents the same boundary | ||
| from being added to the stack more than once. | ||
| """ | ||
| class Trie: | ||
| def __init__(self): | ||
| self.children = dict() | ||
| self.end = False | ||
|
|
||
| def insert(self, target: int): | ||
| node = self | ||
|
Comment on lines
+101
to
+102
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. target의 타입이 잘못된 거 같습니다.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그렇네요, 파이썬 타입 힌트는 강하게 묶여있지 않아서 실수를 자주 하게 되네요 |
||
|
|
||
| for ch in target: | ||
| if ch not in node.children: | ||
| node.children[ch] = Trie() | ||
| node = node.children[ch] | ||
|
|
||
| node.end = True | ||
|
|
||
| def startsWith(self, target: str, start: int) -> List[int]: | ||
| ans = [] | ||
| node = self | ||
|
|
||
| for i in range(start, len(target)): | ||
| ch = target[i] | ||
|
|
||
| if ch not in node.children: | ||
| return ans | ||
|
|
||
| node = node.children[ch] | ||
| if node.end: | ||
| ans.append(i + 1) | ||
|
|
||
| return ans | ||
|
Comment on lines
+112
to
+125
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ans에는 무슨 값이 담기는 걸까요?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 검색 대상인 target 문자열에서 start부터 보면서 trie에 들어간 문자열중에 예를 들어 wordDict에 abc, teg가 있고 teg가 4번 인덱스부터 시작할때 일치하기 때문에 |
||
|
|
||
|
|
||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
| S = len(s) | ||
| t = Trie() | ||
| visited = [False] * (S + 1) | ||
|
|
||
| for word in wordDict: | ||
| t.insert(word) | ||
|
|
||
| stack = [0] | ||
|
|
||
| while stack: | ||
| start = stack.pop() | ||
|
|
||
| starts_with = t.startsWith(s, start) | ||
|
|
||
| for start_index in starts_with: | ||
| if start_index == S: | ||
| return True | ||
| if visited[start_index]: | ||
| continue | ||
| visited[start_index] = True | ||
| stack.append(start_index) | ||
|
|
||
| return False | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 최적의 해를 얻으려면 현재까지의 최저가와 지금의 가격 차이를 비교해 최대 이익을 업데이트한다.
개선 제안: 현재 구현이 적절해 보입니다.