diff --git a/best-time-to-buy-and-sell-stock/river20s.py b/best-time-to-buy-and-sell-stock/river20s.py new file mode 100644 index 000000000..ce18b6901 --- /dev/null +++ b/best-time-to-buy-and-sell-stock/river20s.py @@ -0,0 +1,26 @@ +class Solution(object): + def maxProfit(self, prices): + """ + :type prices: List[int] + :rtype: int + 주어진 prices를 한 번만 순회하면서 + 지금까지의 최솟값을 추적하고, + '현재 가격 - 지금까지의 최솟값'으로 계산되는 이익이 + '지금까지의 최대 이익(초기 0)보다 크면 갱신하여 + 최종 최대 이익 구하기 문제 + Time Complexity: O(n) + Space Complexity: O(1) + """ + max_profit = 0 # 이익이 없을 때 0을 반환하게 초기화 + min_price = float('inf') # 최소 가격 저장, 무한대로 초기화해서 루프 첫 번째 가격부터 최소 가격에 저장 + + for price in prices: + # 최대 이익 갱신 + max_profit = max(max_profit, (price - min_price)) + # 최소 가격 갱신 + min_price = min(min_price, price) + # 최대 이익(max_profit) 갱신 이후 최소 가격(min_price) 갱신해야 함 + # 최대 이익 자체는 이미 '산' 주식에 대해 계산해야 하므로 + # 사는 동시 팔 수 없음 + + return max_profit diff --git a/group-anagrams/river20s.py b/group-anagrams/river20s.py new file mode 100644 index 000000000..8128c773b --- /dev/null +++ b/group-anagrams/river20s.py @@ -0,0 +1,32 @@ +import collections +from typing import List + +class Solution(object): + def groupAnagrams(self, strs: List[str]) -> List[List[str]]: + """ + :type strs: List[str] + :rtype: List[List[str]] + 각 문자열을 순회하면서 알파벳 순으로 정렬한 결과를 tuple로 변환하여 Key로 하고, + 그 Key의 원래 문자열을 Value로 하는 딕셔너리를 통해 애너그램을 그룹화 하는 문제 + 딕셔너리의 Value만 모아서 반환함 + Time Complexity: O(N*K log K) (단, N은 입력 리스트 안의 문자열 개수(리스트 길이), K는 문자열 하나 당 길이) + -> 정렬에 O(K log K)시간 소요, 정렬 작업은 리스트 안의 문자열 개수 N만큼 반복 + Space Complexity: O(N*K) + -> 딕셔너리를 통해 원본 문자열 N개가 모두 저장됨 + """ + # collections의 defaultdict 사용하여 Key 없을 때 자동으로 빈 리스트 생성함 + # Key: 정렬된 문자열, Value: 원래 문자열의 리스트 + anagram_groups = collections.defaultdict(list) + + # s는 입력 strs의 각 문자열 + # 모든 s에 대해 순회 + for s in strs: + # s를 알파벳 순으로 정렬하고, Key로 쓰기 위해 튜플로 변환함 + # sorted(s)는 각 문자를 요소로 갖는 리스트(예: ['h', 'i']) + # 튜플로 바꿔서 Key로 쓸 수 있게 하였음 + key = tuple(sorted(s)) + # defaultdict 사용하므로, Key 존재 여부 확인 불필요 + # 해당 Key의 Value에 원래 문자열 s 추가 + anagram_groups[key].append(s) + # 딕셔너리 Value만 모아 반환 + return list(anagram_groups.values()) diff --git a/implement-trie-prefix-tree/river20s.py b/implement-trie-prefix-tree/river20s.py new file mode 100644 index 000000000..d3a98cbaa --- /dev/null +++ b/implement-trie-prefix-tree/river20s.py @@ -0,0 +1,53 @@ +class TrieNode: + # 트라이 노드 + def __init__(self): + self.children = {} + # 단어의 끝을 나타내기 위한 플래그 + self.isEndOfWord = False + +class Trie: + # 트라이 + def __init__(self): + self.root = TrieNode() + + def insert(self, word: str) -> None: + # 삽입 연산 + # Time Complexity: O(L) (L은 word의 길이) + # Space Complexity: O(M) (M은 트라이에 삽입된 모든 단어들의 총 문자 개수 합) + currentNode = self.root + + for char in word: + if char not in currentNode.children: + currentNode.children[char] = TrieNode() + currentNode = currentNode.children[char] + currentNode.isEndOfWord = True + + def search(self, word: str) -> bool: + # 완전 검색 연산 + # Time Complexity: O(L) (L은 word의 길이) + # Space Complexity: O(M) + currentNode = self.root + for char in word: + if char not in currentNode.children: + return False + currentNode = currentNode.children[char] + return currentNode.isEndOfWord + + def startsWith(self, prefix: str) -> bool: + # 접두사 일치 검사 연산 + # Time Complexity: O(P) (P는 Prefix의 길이) + # Space Complexity: O(M) + currentNode = self.root + for char in prefix: + if char not in currentNode.children: + return False + currentNode = currentNode.children[char] + return True + + + +# Your Trie object will be instantiated and called as such: +# obj = Trie() +# obj.insert(word) +# param_2 = obj.search(word) +# param_3 = obj.startsWith(prefix) diff --git a/word-break/river20s.py b/word-break/river20s.py new file mode 100644 index 000000000..d73a033f7 --- /dev/null +++ b/word-break/river20s.py @@ -0,0 +1,78 @@ +class TrieNode: + def __init__(self): + self.children = {} + self.isEndOfWord = False + +class Trie: + def __init__(self): + self.root = TrieNode() + + def insert(self, word: str) -> None: + currentNode = self.root + for char in word: + if char not in currentNode.children: + currentNode.children[char] = TrieNode() + currentNode = currentNode.children[char] + currentNode.isEndOfWord = True + +# <-- 여기까지 Trie 구현을 위한 TrieNode와 Trie 클래스 +# --> 여기부터 Word Break 문제 푸는 Solution 클래스 + +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + + # 1. 트라이 구축 + # wordDict 모든 단어 -> 트라이 넣기 + trie = Trie() + for word in wordDict: + trie.insert(word) + + n = len(s) # 문자열 s의 길이, 나중에 인덱스 끝까지 도달했는지 확인하기 위해 사용함 + + # <<<--- 메모이제이션 캐시 초기화 ---<<< + # key: start_index, value: s[start_index:] 분할 가능 여부 (True/False) + memo = {} + + # 2. 재귀 함수 정의 + # canBreak(start_index): s[strat_index:] 부분을 분할할 수 있는지 확인 + def canBreak(start_index: int) -> bool: + + # <<<--- 캐시 확인 ---<<< + # 이 start_index에 대한 결과가 이미 memo에 있으면 바로 반환 + if start_index in memo: + return memo[start_index] + + # 베이스 케이스 + # 시작 인덱스(start_index)가 문자열 끝에 도달했다면 성공 + if start_index == n: + return True + + # 현재 start_index부터 시작하는 가능한 모든 단어를 트라이를 이용해 찾고 + # 각 단어에 대해 나머지 부분이 분할 가능한지 재귀적으로 확인 + currentNode = trie.root + for i in range(start_index, n): + char = s[i] + + # 현재 문자가 트라이 경로에 없다면 해당 트라이 탐색은 더이상 진행하지 않음 + if char not in currentNode.children: + break + + # 트라이의 다음 노드로 이동 + currentNode = currentNode.children[char] + + # 이동한 노드가 단어의 끝이라면 + if currentNode.isEndOfWord: + # 나머지 부분 s[i+1:]에 대해서도 분할 가능한지 재귀 호출 + if canBreak(i + 1): + # 나머지 부분 분할 성공 => 전체 분할 가능 + # <<<--- 성공 결과 캐시에 저장 ---<<< + memo[start_index] = True + return True + # start_index부터 시작하는 모든 가능한 단어 분할을 시도했으나 + # 성공적인 경로를 찾지 못했다면 + # <<<--- 실패 결과 캐시에 저장 ---<<< + memo[start_index] = False + return False + + # 3. 재귀 함수 호출 시작 + return canBreak(0)