diff --git a/best-time-to-buy-and-sell-stock/okyungjin.py b/best-time-to-buy-and-sell-stock/okyungjin.py new file mode 100644 index 0000000000..83687e5126 --- /dev/null +++ b/best-time-to-buy-and-sell-stock/okyungjin.py @@ -0,0 +1,28 @@ +''' +[문제] +- 주식 가격이 담긴 정수 배열 prices가 주어진다. +- 특정 날짜에 주식을 단 한 번 구매하고, 미래의 특정 날짜에 판매하여 얻을 수 있는 최대 이익을 구한다. +- 이익을 낼 수 없는 경우 0을 반환하며, 반드시 구매 후 판매해야 한다. + +[풀이] +1. prices 배열을 순회하며 최소 가격을 min_price에 기록한다. +2. 동시에 현재 가격에서 min_price를 뺀 이익이 최대인지 확인하여 갱신한다. +3. 최대 이익을 반환한다. + +[복잡도] +시간 복잡도: O(N) +공간 복잡도: O(1) +''' +class Solution: + def maxProfit(self, prices): + min_price = prices[0] + max_profit = 0 + + for i in range(1, len(prices)): + if prices[i] < min_price: + min_price = prices[i] + + elif prices[i] - min_price > max_profit: + max_profit = prices[i] - min_price + + return max_profit diff --git a/encode-and-decode-strings/okyungjin.py b/encode-and-decode-strings/okyungjin.py new file mode 100644 index 0000000000..7303029fc5 --- /dev/null +++ b/encode-and-decode-strings/okyungjin.py @@ -0,0 +1,67 @@ +""" +Design an algorithm to encode a list of strings to a string. +The encoded string is then sent over the network and is decoded back to the original list of strings. + +Example 1: + Input: List[str] = ["Hello","World"] + Output: ["Hello","World"] + +Example 2: + Input: List[str] = [] + Output: [] + +Constraints: + 0 <= strs.length < 100 + 0 <= strs[i].length < 200 + strs[i] contains any possible characters out of 256 valid ASCII characters. +""" +from typing import Final, List + +""" +접근법: + encode: 각 단어 앞에 글자수 + 구분자를 붙여 하나의 문자열로 이어 붙입니다. + decode: 문자열을 앞에서부터 읽으면서 구분자를 찾고, 그 앞의 숫자로 '글자수'를 알아낸 뒤 정확히 그 길이만큼만 잘라내어 원래 리스트로 복원합니다. + +복잡도: + 시간 복잡도: O(N) + 공간 복잡도: O(N) +""" +class Solution: + # 구분자 심볼 + DELIMITER: Final[str] = '#' + + """ + `글자수 + 구분자 + 단어` 조합으로 인코딩한 후 이어붙여 반환합니다. + + Example: + Input: ["Hello","World"] + Return: "5#Hello5#World" + + Input: [] + Return: "" + + Input: [""] + Return: "0#" + """ + def encode(self, strs: List[str]) -> str: + return ''.join(f"{len(s)}{self.DELIMITER}{s}" for s in strs) + + + """ + 1. 현재 위치부터 탐색하여 첫 번째 구분자(#)의 위치를 찾습니다. + 2. 구분자 바로 앞의 숫자를 읽어내어 잘라낼 단어의 길이를 파악합니다. + 3. 구분자 다음 위치부터 파악한 길이만큼 문자열을 잘라내어 결과 리스트에 담습니다. + 4. 문자열 끝까지 위 과정을 반복한 후, 최종 리스트를 반환합니다. + """ + def decode(self, s: str) -> List[str]: + result = [] + idx = 0 + + while idx < len(s): + delimiter_idx = s.find(self.DELIMITER, idx) + length = int(s[idx : delimiter_idx]) + + idx = delimiter_idx + 1 + length + result.append(s[delimiter_idx + 1 : idx]) + + return result diff --git a/group-anagrams/okyungjin.py b/group-anagrams/okyungjin.py new file mode 100644 index 0000000000..88fbf5dd22 --- /dev/null +++ b/group-anagrams/okyungjin.py @@ -0,0 +1,44 @@ +''' +[복잡도] +n: strs의 길이, k: 각 문자열의 최대 길이 + +시간 복잡도: O(n * k log k) +공간 복잡도: O(n * k) +''' +class Solution: + def groupAnagrams(self, strs: List[str]) -> List[List[str]]: + anagram_map: dict[str, List[str]] = {} + + for s in strs: + key = ''.join(sorted(s)) + + if key in anagram_map: + anagram_map[key].append(s) + else: + anagram_map[key] = [s] + + return list(anagram_map.values()) + + +''' +solution2: 문자열의 빈도수 배열을 해시의 키로 사용 + +복잡도: + n: strs의 길이, k: 각 문자열의 최대 길이 + 시간 복잡도: O(n * k) + 공간 복잡도: O(n * k) +''' +from collections import defaultdict + +class Solution: + def groupAnagrams(self, strs: List[str]) -> List[List[str]]: + anagram_map: dict[tuple, List[str]] = defaultdict(list) + + for s in strs: + counts = [0] * 26 + for char in s: + counts[ord(char) - ord('a')] += 1 + + anagram_map[tuple(counts)].append(s) + + return list(anagram_map.values()) diff --git a/implement-trie-prefix-tree/okyungjin.py b/implement-trie-prefix-tree/okyungjin.py new file mode 100644 index 0000000000..74e4fbe3b3 --- /dev/null +++ b/implement-trie-prefix-tree/okyungjin.py @@ -0,0 +1,80 @@ +class Trie: + + def __init__(self): + self.word_set = set() + self.prefix_set = set() + + + def insert(self, word: str) -> None: + if word in self.word_set: + return + + self.word_set.add(word) + + prefix = '' + for char in word: + prefix += char + self.prefix_set.add(prefix) + + + def search(self, word: str) -> bool: + return word in self.word_set + + + def startsWith(self, prefix: str) -> bool: + return prefix in self.prefix_set + + +''' +solution2: TrieNode 자료구조 활용 +''' +class TrieNode: + def __init__(self): + self.children: dict[str, 'TrieNode'] = {} + self.is_leaf = False + +class Trie: + def __init__(self): + self.root = TrieNode() + + ''' + 시간 복잡도: O(L), L: 단어의 길이 + 공간 복잡도: O(L), 겹치는 문자열이 없을 때 TrideNode N개 생성 + ''' + def insert(self, word: str) -> None: + node = self.root + + for char in word: + if char not in node.children: + node.children[char] = TrieNode() + node = node.children[char] + + node.is_leaf = True + + ''' + 시간 복잡도: O(L), L: 단어의 길이 + 공간 복잡도: O(1) + ''' + def search(self, word: str) -> bool: + node = self.root + + for char in word: + if char not in node.children: + return False + node = node.children[char] + + return node.is_leaf + + ''' + 시간 복잡도: O(L), L: 단어의 길이 + 공간 복잡도: O(1) + ''' + def startsWith(self, prefix: str) -> bool: + node = self.root + + for char in prefix: + if char not in node.children: + return False + node = node.children[char] + + return True diff --git a/word-break/okyungjin.py b/word-break/okyungjin.py new file mode 100644 index 0000000000..dcb7975aec --- /dev/null +++ b/word-break/okyungjin.py @@ -0,0 +1,156 @@ +''' +solution1: dfs + @cache +''' +from typing import List +from functools import cache + +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + size = len(s) + word_set = set(wordDict) + + @cache + def dfs(start: int) -> bool: + if start == size: + return True + + for end in range(start + 1, size + 1): + word = s[start:end] # O(N) + + if word in word_set and dfs(end): + return True + + return False + + return dfs(0) + +''' +solution2: dfs + memo +''' +class Solution: + def wordBreak(self, s: str, wordDict: list[str]) -> bool: + size = len(s) + word_set = set(wordDict) + failed: set[int] = set() # 탐색에 실패한 인덱스 기록 + + def dfs(start: int) -> bool: + if start == size: + return True + + if start in failed: + return False + + for end in range(start + 1, size + 1): + word = s[start:end] + + if word in word_set and dfs(end): + return True + + failed.add(start) + return False + + return dfs(0) + +''' +solution3: bfs +''' +from collections import deque + +class Solution: + def wordBreak(self, s: str, wordDict: list[str]) -> bool: + size = len(s) + word_set = set(wordDict) + queue = deque([0]) + visited = set([0]) + + while queue: + start = queue.popleft() + + for end in range(start + 1, size + 1): + word = s[start:end] + + if end not in visited and word in word_set: + if end == size: + return True + + queue.append(end) + visited.add(end) + + return False + +''' +solution4: dp +''' +class Solution: + def wordBreak(self, s: str, wordDict: list[str]) -> bool: + size = len(s) + word_set = set(wordDict) + dp = [False] * (size + 1) + dp[0] = True + + for end in range(1, size + 1): + for start in range(end): + word = s[start:end] + + if dp[start] and word in word_set: + dp[end] = True + break + + return dp[-1] + +''' +solution5: trie 자료구조 활용 + +Trie 자료구조로 노드를 만들어서 탐색한다 +Examples3: s = "catsandog", wordDict = ["cats","dog","sand","and","cat"] + +[Root] + ├── 'c' ── 'a' ── 't'(leaf) ── 's'(leaf) + ├── 'd' ── 'o' ── 'g'(leaf) + ├── 's' ── 'a' ── 'n' ── 'd'(leaf) + └── 'a' ── 'n' ── 'd'(leaf) +''' +class TrieNode: + def __init__(self): + self.children: dict[int, 'TrieNode'] = {} + self.is_leaf = False + +class Solution: + def wordBreak(self, s: str, wordDict: list[str]) -> bool: + root = self._build_trie(wordDict) + + size = len(s) + dp = [False] * (size + 1) + dp[0] = True + + for start in range(size): + if not dp[start]: + continue + + node = root + for end in range(start + 1, size + 1): + char = s[end - 1] + # trie에 없으면 바로 종료 + if char not in node.children: + break + node = node.children[char] + + if node.is_leaf: + dp[end] = True + + return dp[-1] + + # wordDict로 trie 자료구조 생성 + def _build_trie(self, wordDict: list[str]) -> TrieNode: + root = TrieNode() + + for word in wordDict: + node = root + + for char in word: + if char not in node.children: + node.children[char] = TrieNode() + node = node.children[char] + node.is_leaf = True + + return root