diff --git a/best-time-to-buy-and-sell-stock/pmjuu.py b/best-time-to-buy-and-sell-stock/pmjuu.py new file mode 100644 index 000000000..a9904882f --- /dev/null +++ b/best-time-to-buy-and-sell-stock/pmjuu.py @@ -0,0 +1,21 @@ +# 시간 복잡도: +# - 배열을 한 번 순회하므로 O(n) (n은 prices 배열의 길이) + +# 공간 복잡도: +# - 추가 변수 2개(min_price, max_profit)를 사용하므로 O(1) + +from typing import List + + +class Solution: + def maxProfit(self, prices: List[int]) -> int: + buy = float('inf') + profit = 0 + + for price in prices: + if price < buy: + buy = price + else: + profit = max(profit, price - buy) + + return profit diff --git a/encode-and-decode-strings/pmjuu.py b/encode-and-decode-strings/pmjuu.py new file mode 100644 index 000000000..01918b865 --- /dev/null +++ b/encode-and-decode-strings/pmjuu.py @@ -0,0 +1,71 @@ +# encode 메서드: +# 시간 복잡도: O(N) +# - 각 문자열을 한 번씩 순회하며 길이와 내용을 처리하므로, 모든 문자열 길이의 합에 비례. +# 공간 복잡도: O(N) +# - 인코딩된 결과 문자열을 저장하기 위해 추가 공간 사용. + +# decode 메서드: +# 시간 복잡도: O(N) +# - 인코딩된 문자열을 처음부터 끝까지 한 번 순회하며 파싱하므로, 입력 문자열 길이에 비례. +# - s.find('#', i)는 인덱스 i부터 다음 #를 찾음. +# - 여러 번의 find 호출이 있더라도 각 호출마다 전체 문자열을 다시 탐색하지 않고 이전 탐색 이후의 부분만 탐색하게 되어, 모든 find 호출을 합한 전체 탐색 거리는 인코딩된 문자열의 전체 길이 n에 해당함. 전체 시간은 O(n)에 수렴. +# 공간 복잡도: O(N) +# - 디코딩된 문자열 리스트를 구성하기 위해 추가 공간 사용. + + +class Solution: + """ + @param strs: a list of strings + @return: encodes a list of strings to a single string. + """ + def encode(self, strs): + encoded_str = "" + + for s in strs: + encoded_str += (str(len(s)) + "#" + s) + + return encoded_str + + """ + @param s: A string + @return: decodes a single string to a list of strings + """ + def decode(self, s): + decoded_list = [] + i = 0 + while i < len(s): + j = s.find("#", i) + length = int(s[i:j]) + i = j + 1 + decoded_list.append(s[i:i + length]) + i += length + + return decoded_list + + +def main(): + sol = Solution() + + # 테스트 예시 1 + input_strings1 = ["lint", "code", "love", "you"] + print("===== Example 1 =====") + print("Original: ", input_strings1) + encoded_str1 = sol.encode(input_strings1) + print("Encoded: ", encoded_str1) + decoded_list1 = sol.decode(encoded_str1) + print("Decoded: ", decoded_list1) + print() + + # 테스트 예시 2 + input_strings2 = ["1234567890a", "we", "say", "#", "yes"] + print("===== Example 2 =====") + print("Original: ", input_strings2) + encoded_str2 = sol.encode(input_strings2) + print("Encoded: ", encoded_str2) + decoded_list2 = sol.decode(encoded_str2) + print("Decoded: ", decoded_list2) + print() + + +if __name__ == "__main__": + main() diff --git a/group-anagrams/pmjuu.py b/group-anagrams/pmjuu.py new file mode 100644 index 000000000..4b2024aa6 --- /dev/null +++ b/group-anagrams/pmjuu.py @@ -0,0 +1,22 @@ +# 시간 복잡도: O(n * m log m) +# - n: 문자열 배열의 길이, m: 각 문자열의 최대 길이 +# - 각 문자열을 정렬하는 데 O(m log m) 시간이 걸리며, 모든 문자열에 대해 이를 수행 +# - 문제에서 m 이 최대 100이므로, 정렬 비용은 상수에 가깝게 볼 수도 있음 (m = 100인 상황에서 100 * log(100)는 약 664 정도) + +# 공간 복잡도: O(n * m) +# - 정렬된 문자열(길이 최대 m)을 키로 사용하는 딕셔너리에 최대 n개의 키가 생길 수 있음 +# - 각 키에 대해 원본 문자열들을 저장하므로 O(n * m) 크기의 공간을 사용 + +from collections import defaultdict +from typing import List + + +class Solution: + def groupAnagrams(self, strs: List[str]) -> List[List[str]]: + anagram_map = defaultdict(list) + + for word in strs: + sorted_word = ''.join(sorted(word)) + anagram_map[sorted_word].append(word) + + return list(anagram_map.values()) diff --git a/implement-trie-prefix-tree/pmjuu.py b/implement-trie-prefix-tree/pmjuu.py new file mode 100644 index 000000000..7368a0f7d --- /dev/null +++ b/implement-trie-prefix-tree/pmjuu.py @@ -0,0 +1,55 @@ +# 시간 복잡도: +# - insert(word): O(m) +# - m은 word의 길이입니다. +# - 각 문자에 대해 트리를 탐색하거나 새 노드를 추가하므로 O(m) 시간이 걸립니다. +# +# - search(word): O(m) +# - m은 word의 길이입니다. +# - 트리의 각 문자 노드를 탐색하므로 O(m) 시간이 걸립니다. +# +# - startsWith(prefix): O(m) +# - m은 prefix의 길이입니다. +# - 트리의 각 문자 노드를 탐색하므로 O(m) 시간이 걸립니다. +# +# 공간 복잡도: +# - O(n * m) +# - n은 트리에 삽입된 단어의 개수입니다. +# - m은 각 단어의 평균 길이입니다. +# - 각 단어의 문자에 대해 새로운 노드를 생성하므로 O(n * m) 공간이 필요합니다. + + +class Trie: + + def __init__(self): + self.children = {} + self.is_end_of_word = False + + def insert(self, word: str) -> None: + current = self + + for char in word: + if char not in current.children: + current.children[char] = Trie() + current = current.children[char] + + current.is_end_of_word = True + + def search(self, word: str) -> bool: + current = self + + for char in word: + if char not in current.children: + return False + current = current.children[char] + + return current.is_end_of_word + + def startsWith(self, prefix: str) -> bool: + current = self + + for char in prefix: + if char not in current.children: + return False + current = current.children[char] + + return True diff --git a/word-break/pmjuu.py b/word-break/pmjuu.py new file mode 100644 index 000000000..c4bb66cd3 --- /dev/null +++ b/word-break/pmjuu.py @@ -0,0 +1,30 @@ +""" +시간 복잡도: +- 외부 루프는 O(n), 내부 루프는 최악의 경우 각 i에 대해 O(n)만큼 실행됩니다. (n: 문자열 s의 길이) +- s[j:i]를 wordSet에서 찾는 작업은 O(1)입니다 (set 사용). +- 전체 시간 복잡도: O(n^2). + +공간 복잡도: +- DP 배열은 O(n)의 공간을 차지합니다. +- wordSet은 wordDict에 있는 모든 문자의 총 길이를 기준으로 O(m)의 공간을 차지합니다. (m: wordDict의 단어 수) +- 전체 공간 복잡도: O(n + m). +""" + +from typing import List + + +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + wordSet = set(wordDict) + n = len(s) + + dp = [False] * (n + 1) + dp[0] = True + + for i in range(1, n + 1): + for j in range(i): + if dp[j] and s[j:i] in wordSet: + dp[i] = True + break + + return dp[n] diff --git a/word-search/pmjuu.py b/word-search/pmjuu.py index a119c032a..b04ed975f 100644 --- a/word-search/pmjuu.py +++ b/word-search/pmjuu.py @@ -51,13 +51,14 @@ def search(row, col, word_idx, visited): # => 총 공간 복잡도: O(k) +from collections import Counter + class Solution: def exist(self, board: list[list[str]], word: str) -> bool: n, m = len(board), len(board[0]) word_length = len(word) # 조기 종료: board에 word를 구성할 충분한 문자가 있는지 확인 - from collections import Counter board_counter = Counter(char for row in board for char in row) word_counter = Counter(word) if any(word_counter[char] > board_counter[char] for char in word_counter):