From b909f6085d45edd42dc50ce6f70382c2901281f6 Mon Sep 17 00:00:00 2001 From: seungriyou Date: Mon, 28 Apr 2025 19:15:50 +0900 Subject: [PATCH 1/5] solve(w05): 121. Best Time to Buy and Sell Stock --- best-time-to-buy-and-sell-stock/seungriyou.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 best-time-to-buy-and-sell-stock/seungriyou.py diff --git a/best-time-to-buy-and-sell-stock/seungriyou.py b/best-time-to-buy-and-sell-stock/seungriyou.py new file mode 100644 index 000000000..4bc807862 --- /dev/null +++ b/best-time-to-buy-and-sell-stock/seungriyou.py @@ -0,0 +1,48 @@ +# https://leetcode.com/problems/best-time-to-buy-and-sell-stock/ + +from typing import List + +class Solution: + def maxProfit1(self, prices: List[int]) -> int: + """ + [Complexity] + - TC: O(n) + - SC: O(1) + + [Approach] + prices를 순회하면서 다음을 트래킹하면 된다. + (1) 지금까지의 최소 가격인 min_price 트래킹: min(min_price, price) + (2) 지금까지의 최대 수익인 max_profit 트래킹: max(max_profit, price - min_price) + """ + max_profit, min_price = 0, prices[0] + + for price in prices: + min_price = min(min_price, price) + max_profit = max(max_profit, price - min_price) + + return max_profit + + def maxProfit(self, prices: List[int]) -> int: + """ + [Complexity] + - TC: O(n) + - SC: O(1) + + [Approach] + two-pointer(buy, sell)로도 접근 가능하다. + 두 개의 pointer buy와 sell을 0, 1 인덱스에서 시작한 후, sell을 움직여가며 curr_profit = prices[sell] - prices[buy]를 구한다. + 그리고 curr_profit이 0보다 크면 max_profit을 트래킹하고, 그렇지 않으면 buy를 sell 위치로 당긴다. + (curr_profit이 0보다 크지 않다는 것은, buy 시점의 price 이하인 price가 sell 시점에서 발견되었다는 뜻이다!) + """ + buy, sell = 0, 1 + max_profit = 0 + + while sell < len(prices): + curr_profit = prices[sell] - prices[buy] + if curr_profit > 0: + max_profit = max(max_profit, curr_profit) + else: + buy = sell + sell += 1 + + return max_profit From 85a811c82c6b9872aa7775b6e457ca17a58c5932 Mon Sep 17 00:00:00 2001 From: seungriyou Date: Tue, 29 Apr 2025 22:36:27 +0900 Subject: [PATCH 2/5] solve(w05): 49. Group Anagrams --- group-anagrams/seungriyou.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 group-anagrams/seungriyou.py diff --git a/group-anagrams/seungriyou.py b/group-anagrams/seungriyou.py new file mode 100644 index 000000000..7755479fa --- /dev/null +++ b/group-anagrams/seungriyou.py @@ -0,0 +1,24 @@ +# https://leetcode.com/problems/group-anagrams/ + +from typing import List + +class Solution: + def groupAnagrams(self, strs: List[str]) -> List[List[str]]: + """ + [Approach] + - TC: O(n * klogk) (n = len(strs), k = max length of an element) + - SC: O(n * k) + + [Complexity] + (1) strs의 원소들을 정렬해서 (2) hash table의 key 값으로 사용하면 + 매 단계마다 O(1)에 anagram을 찾을 수 있다. + 이때, key 값으로는 list는 사용할 수 없고, string이나 tuple을 사용해야 한다. + """ + from collections import defaultdict + + anagrams = defaultdict(list) + + for s in strs: + anagrams["".join(sorted(s))].append(s) + + return list(anagrams.values()) From 95b89d94d6fea1aa272d53713374170c2ff3ff4f Mon Sep 17 00:00:00 2001 From: seungriyou Date: Sat, 3 May 2025 00:46:28 +0900 Subject: [PATCH 3/5] solve(w05): 208. Implement Trie (Prefix Tree) --- implement-trie-prefix-tree/seungriyou.py | 48 ++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 implement-trie-prefix-tree/seungriyou.py diff --git a/implement-trie-prefix-tree/seungriyou.py b/implement-trie-prefix-tree/seungriyou.py new file mode 100644 index 000000000..d4e0a5a63 --- /dev/null +++ b/implement-trie-prefix-tree/seungriyou.py @@ -0,0 +1,48 @@ +# https://leetcode.com/problems/implement-trie-prefix-tree/ + +from collections import defaultdict + + +class TrieNode: + def __init__(self): + self.is_word = False + self.children = defaultdict(TrieNode) + + +class Trie: + def __init__(self): + self.root = TrieNode() + + def insert(self, word: str) -> None: + curr = self.root + + for w in word: + curr = curr.children[w] + + curr.is_word = True + + def search(self, word: str) -> bool: + curr = self.root + + for w in word: + if w not in curr.children: + return False + curr = curr.children[w] + + return curr.is_word + + def startsWith(self, prefix: str) -> bool: + curr = self.root + + for p in prefix: + if p not in curr.children: + return False + curr = curr.children[p] + + 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) From 02ea8e90e2a40f683787e4689983ec58a3bd5aad Mon Sep 17 00:00:00 2001 From: seungriyou Date: Sat, 3 May 2025 00:46:54 +0900 Subject: [PATCH 4/5] solve(w05): 139. Word Break --- word-break/seungriyou.py | 131 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 word-break/seungriyou.py diff --git a/word-break/seungriyou.py b/word-break/seungriyou.py new file mode 100644 index 000000000..a8409809c --- /dev/null +++ b/word-break/seungriyou.py @@ -0,0 +1,131 @@ +# https://leetcode.com/problems/word-break/ + +from typing import List + +class Solution: + def wordBreak_bfs(self, s: str, wordDict: List[str]) -> bool: + """ + [Complexity] + - TC: O(n^3 + m * k) (n = len(s), m = len(wordDict), k = avg(len(word) for word in wordDict)) + - O(n^3): (1) s[start:end] slicing * (2) end * (3) start + - O(m * k): set(wordDict) + - SC: O(n + m * k) + - O(n): seen, q + - O(m * k): set(wordDict) + + [Approach] + BFS로 접근할 수 있다. + 이때, queue는 확인할 substring의 시작 index를 기록하면 되며, seen을 이용하여 각 start 당 한 번씩만 확인한다. + """ + from collections import deque + + n, words = len(s), set(wordDict) + + q = deque([0]) # 확인할 substring의 시작 index + seen = set() # 중복 X + + while q: + start = q.popleft() + + # base condition + if start == n: + return True + + # string slicing에서 사용할 끝 index를 순회하며 확인 + for end in range(start + 1, n + 1): + # (1) 방문하지 않았으며 (2) wordDict에 현재 보고 있는 substring이 있는 경우 + if end not in seen and s[start:end] in words: + q.append(end) + seen.add(end) + + return False + + def wordBreak_dp(self, s: str, wordDict: List[str]) -> bool: + """ + [Complexity] + - TC: O(n^3 + m * k) + - O(n^3): (1) s[start:end] slicing * (2) end * (3) start + - O(m * k): set(wordDict) + - SC: O(n + m * k) + - O(n): dp + - O(m * k): set(wordDict) + + [Approach] + DP로 접근할 수 있다. + 이때, dp[i] = s[i:]가 wordDict의 원소만으로 구성될 수 있는지 여부이다. + 따라서 핵심은 뒤에서부터 확인하는 것이다! + """ + + n, words = len(s), set(wordDict) + + # dp[i] = s[i:]가 wordDict의 원소만으로 구성될 수 있는지 여부 + dp = [False] * (n + 1) + dp[n] = True + + for start in range(n - 1, -1, -1): + for end in range(start + 1, n + 1): + # (1) s[end:]가 wordDict 원소로 구성 가능하고 (2) wordDict에 현재 보고 있는 substring이 있는 경우 + if dp[end] and s[start:end] in words: + dp[start] = True + break + + return dp[0] + + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + """ + [Complexity] + - TC: O(n^2 + m * k) + - O(n^2): 루프 수행 (slicing 제거) + - O(m * k): trie 생성 + - SC: O(n + m * k) + - O(n): dp list + - O(m * k): trie + + [Approach] + trie를 이용하면, dp에서 매 루프마다 s[start:end] slicing으로 O(n)이 소요되던 것을 O(1)로 최적화 할 수 있다. + """ + from collections import defaultdict + + class TrieNode: + def __init__(self): + self.is_word = False + self.children = defaultdict(TrieNode) + + def add_word(self, word): + curr = self + + for w in word: + curr = curr.children[w] + + curr.is_word = True + + # 1. trie 구성하기 + root = TrieNode() + for word in wordDict: + root.add_word(word) + + # 2. dp 준비하기 + n, words = len(s), set(wordDict) + # dp[i] = s[i:]가 wordDict의 원소만으로 구성될 수 있는지 여부 + dp = [False] * (n + 1) + dp[n] = True + + # 3. trie를 이용하여 최적화된 dp 수행 + for start in range(n - 1, -1, -1): + curr = root + for end in range(start + 1, n + 1): # -- trie 사용으로 각 단계 O(n) -> O(1) + w = s[end - 1] + + # 현재 보고 있는 substring의 마지막 문자가 curr.children에 없다면 중단 + if w not in curr.children: + break + + # 있다면 타고 내려가기 + curr = curr.children[w] + + # (1) s[end:]가 wordDict 원소로 구성 가능하고 (2) wordDict의 word가 발견되었다면 dp 리스트에 기록 후 중단 + if dp[end] and curr.is_word: + dp[start] = True + break + + return dp[0] From c1cb3e10e6162ccb3064ee97de062a1ee971959c Mon Sep 17 00:00:00 2001 From: seungriyou Date: Sat, 3 May 2025 09:23:12 +0900 Subject: [PATCH 5/5] solve(w05): 271. Encode and Decode Strings --- encode-and-decode-strings/seungriyou.py | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 encode-and-decode-strings/seungriyou.py diff --git a/encode-and-decode-strings/seungriyou.py b/encode-and-decode-strings/seungriyou.py new file mode 100644 index 000000000..0acd90706 --- /dev/null +++ b/encode-and-decode-strings/seungriyou.py @@ -0,0 +1,45 @@ +# https://leetcode.com/problems/encode-and-decode-strings/ + +from typing import List + +class Codec: + def encode(self, strs: List[str]) -> str: + """Encodes a list of strings to a single string. + + [Approach] + (length + delimiter + string)의 형태로 str를 구성하면 length 만큼 포인터를 건너뛰며 확인할 수 있게 된다. + delimiter는 숫자(length) 바로 뒤에 처음으로 나오는 문자여야 하므로, 숫자가 아닌 값으로 해야 한다. (나는 "."으로) + """ + # length + delimiter + string + return f"".join(str(len(s)) + "." + s for s in strs) + + def decode(self, s: str) -> List[str]: + """Decodes a single string to a list of strings. + """ + strs = [] + + start = 0 + while start < len(s): + # 1. start 이후에 나오는 첫 delimiter 위치 찾기 + # for delim in range(start, len(s)): + # if s[delim] == ".": + # break + delim = s.find(".", start) + + # 2. 보고 있는 str의 length 구하기 + length = int(s[start:delim]) + + # 3. 보고 있는 str의 다음 위치 구하기 + end = delim + 1 + length + + # 4. 현재 str 모으기 + strs.append(s[delim + 1:end]) + + # 5. start를 end로 이동 + start = end + + return strs + +# Your Codec object will be instantiated and called as such: +# codec = Codec() +# codec.decode(codec.encode(strs))