Skip to content
28 changes: 28 additions & 0 deletions best-time-to-buy-and-sell-stock/okyungjin.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엄청 깔끔하게 잘 해결하셨네요!
best time to buy and sell stock, 즉 해당 문제는
뒤에 로마 숫자를 붙혀서 1, 2, 3, 4, 5 총 다섯종류가 있는데요
dp 연습하기에 정말 괜찮은 문제라고 생각해서
2번문제
II는 한번 풀어보시길 추천드려요!

Comment thread
okyungjin marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -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
67 changes: 67 additions & 0 deletions encode-and-decode-strings/okyungjin.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

덕분에 find(찾을 문자열, 시작 인덱스)typing.Final을 알아가네요!

Comment thread
okyungjin marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -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
44 changes: 44 additions & 0 deletions group-anagrams/okyungjin.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorted를 쓰는것도 좋지만, 정렬 자체가 N Log N 시간복잡도를 가져서
해시 함수를 구현해서 O(N) 시간복잡도로 구현하는것도 좋은것 같더라구요!

  • 지금 봇에 댓글 달면 사라지는 버그 있어서 혹시 몰라 새로 답니다!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorted를 쓰는것도 좋지만, 정렬 자체가 N Log N 시간복잡도를 가져서 해시 함수를 구현해서 O(N) 시간복잡도로 구현하는것도 좋은것 같더라구요!

  • 지금 봇에 댓글 달면 사라지는 버그 있어서 혹시 몰라 새로 답니다!

@alphaorderly 감사합니다 ㅎㅎ 문제 쫙 풀고 최적화 해보도록 하겠습니다 👍👍👍

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

84fbf28

카운터 리스트를 튜플로 변환한 다음에 키로 사용하도록 최적화했습니다.
알고달레 찾아보니 Counter + fronzenset 방식도 사용이 가능하네요. 피드백 감사합니다!

Comment thread
okyungjin marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -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())
80 changes: 80 additions & 0 deletions implement-trie-prefix-tree/okyungjin.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

문제의 목표에는 꽤나 잘 맞도록 잘 구현해 주셨는데요!
Trie 자료구조가 추구하는 방향과는 전혀 맞지 않는것이 너무 아쉽네요 ㅠㅠ
링크

해당 자료구조는 tree의 형태에 좀 더 가까운 자료구조고, 지금처럼 모든 prefix의 경우를 저장하시지 않으시더라도 최적의 시간으로 구할수 있게 해줘서 좋아요!
꼭!! 구현 해보시길 바랍니다!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

문제의 목표에는 꽤나 잘 맞도록 잘 구현해 주셨는데요! Trie 자료구조가 추구하는 방향과는 전혀 맞지 않는것이 너무 아쉽네요 ㅠㅠ 링크

해당 자료구조는 tree의 형태에 좀 더 가까운 자료구조고, 지금처럼 모든 prefix의 경우를 저장하시지 않으시더라도 최적의 시간으로 구할수 있게 해줘서 좋아요! 꼭!! 구현 해보시길 바랍니다!

@alphaorderly 오 맞아요... 사실 문제를 풀고나서 위키 문서를 보게 되었는데 최초에 생각한 구현이랑 방향성이 많이 다르더라구요. 내일 저녁 먹고 이어서 풀어보겠습니다 감사합니다 🔥

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

52ec553 커밋에서 적용했습니다.
wordBreak에서 이 자료구조를 활용할 수 있더라구요. 피드백 감사합니다!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

두 개의 set()에다가 트리의 구조 정보를 저장하는 방식도 있군요!

구현하신 클래스가 공간 복잡도는 높게 나오지만, 조회 성능이 중요한 문제에서는 좋을 것 같네요!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

직관적으로 떠오른 풀이인데 문제의 요구사항이랑은 멀더라구요.
이전 풀이에는 복잡도를 안 적었는데 조회가 더 빠르네요! 리뷰 감사합니다~!

Comment thread
okyungjin marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -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
156 changes: 156 additions & 0 deletions word-break/okyungjin.py
Comment thread
okyungjin marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와우 다양한 방법으로 풀이를 해주셨네요.

trie 자료구조를 사용하는 방법은 저도 문제를 풀면서 잠깐 생각했다가, 안될 것 같아서 다른 방법을 생각했었는데 dp와 함께 풀 수 있었군요! 많이 배워갑니다!

cache 또는 failed, visited, dp로 이미 수행한 결과를 기록하여, 중복 계산을 없애는 방법도 엄청 좋은 것 같네요!

Original file line number Diff line number Diff line change
@@ -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
Loading