Skip to content

[river20s] WEEK 05 solutions #1391

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 3, 2025
26 changes: 26 additions & 0 deletions best-time-to-buy-and-sell-stock/river20s.py
Original file line number Diff line number Diff line change
@@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

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

"최소 이익 갱신 이후 최소 가격 갱신해야 함" 주석이 실제 코드 순서와 반대로 되어 있습니다.
코드에서는 최대 이익을 먼저 계산한 후 최소 가격을 갱신하고 있으므로, 주석과 맞게 조정하면 좋겠습니다 :)
전반적으로 매우 깔끔하고 효율적인 코드 같습니다. 고생하셨습니다👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

감사합니다. 반영하여서 수정했습니다!

# 최대 이익(max_profit) 갱신 이후 최소 가격(min_price) 갱신해야 함
# 최대 이익 자체는 이미 '산' 주식에 대해 계산해야 하므로
# 사는 동시 팔 수 없음

return max_profit
32 changes: 32 additions & 0 deletions group-anagrams/river20s.py
Original file line number Diff line number Diff line change
@@ -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())
53 changes: 53 additions & 0 deletions implement-trie-prefix-tree/river20s.py
Original file line number Diff line number Diff line change
@@ -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)
78 changes: 78 additions & 0 deletions word-break/river20s.py
Original file line number Diff line number Diff line change
@@ -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)