Skip to content

[taurus09318976] WEEK 05 Solutions #1412

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 6 commits into from
May 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions best-time-to-buy-and-sell-stock/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'''
- 이 문제는 최저가에 한 주식을 사서, 그 이후에 최대가에 팔아야 하는 문제임
- 만약 이익을 낼 수 없으면 0을 반환함
조건 :
1) 반드시 한 번만 사고 한 번만 팔아야 하며, 팔기 전에 반드시 사야 함
2) 팔 때는 구매한 날 이후의 날짜여야 합니다.

Example 1. 의 경우
날짜 (index)| 가격 (price) | min_price (최저가) | current_profit (현재 이익) | max_profit (최대 이익)
0 7 7(초기값) - 0(초기값)
1 1 7 -> 1 - 0
2 5 1 5 - 1 = 4 0 -> 4
3 3 1 3 - 1 = 2 4
4 6 1 6 - 1 = 5 4 -> 5
5 4 1 4 - 1 = 3 5

'''
class Solution:
def maxProfit(self, prices: List[int]):
# 입력 배열이 비어 있으면 주가가 없으므로 거래할 수 없음. 따라서 최대 이익은 0
if not prices:
return 0

# 배열의 첫 번째 가격을 최저가로 초기화함. 이후 가격과 비교할 기준점이 됨
min_price = prices[0]
# 이익이 없으면 0을 반환하기 위해 최대 이익을 0으로 초기화함
max_profit = 0

for price in prices[1:]: # 두 번째 가격부터 순회. 첫번째 가격은 이미 min_price에 할당했으므로 제외함.
if price < min_price: # 현재 가격이 최저가보다 작으면 갱신. 더 싸게 살 수 있는 날을 찾는 과정임
min_price = price
else:
profit = price - min_price # 현재 가격에서 최저가를 빼서, 지금 팔면 얻을 수 있는 이익 계산
if profit > max_profit: # 계산한 이익이 기존의 최대 이익보다 크면 max_profit 갱신
max_profit = profit

return max_profit # 반복이 끝나면 최대 이익을 반환


'''
시간 복잡도 : O(n)
배열을 한 번만 순회하기 때문에 입력 크기 n에 비례함
n은 prices 배열의 길이

공간 복잡도 : O(1)
추가로 사용하는 변수는 min_price, max_profit, profit 3개뿐임(상수 개수),
입력 크기에 따라 달라지지 않음
입력 배열 외에 별도의 저장 공간이 필요하지 않음
'''

47 changes: 47 additions & 0 deletions encode-and-decode-strings/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'''
목표 : 문자열 리스트를 하나의 문자열로 인코딩하고, 다시 원래 리스트로 디코딩하는 알고리듬을 만드는 문제임
핵심 : 문자열에 어떤 문자가 들어 있더라도 정확히 인코딩/디코딩할 수 있어야 함
해결법 : 각 문자열 앞에 길이를 붙여서 인코딩 함

Example 1. 단계별 설명
입력: ["lint","code","love","you"]

인코딩 과정:
"lint" → 4:lint
"code" → 4:code
"love" → 4:love
"you" → 3:you

최종 인코딩 문자열: 4:lint4:code4:love3:you

디코딩 과정:
i=0 → : 위치 1, 길이 4 → 문자열 lint (i=5)
i=5 → : 위치 6, 길이 4 → 문자열 code (i=10)
i=10 → : 위치 11, 길이 4 → 문자열 love (i=15)
i=15 → : 위치 16, 길이 3 → 문자열 you (i=19)

최종 결과: ["lint","code","love","you"]

동작 원리 요약
인코딩: 각 문자열 앞에 길이를 붙여서 혼동 없이 디코딩 가능함
디코딩: 길이 정보를 이용해 정확히 문자열을 추출함
'''


class Solution:
def encode(self, strs):
encoded = []
for s in strs:
encoded.append(f"{len(s)}:{s}") # 각 문자열 앞에 길이 + ":"을 붙임
return "".join(encoded) # 모든 문자열을 하나로 합침

def decode(self, s):
decoded = []
i = 0
while i < len(s):
colon = s.find(":", i) # s.find(":", i) : 현재 위치(i)부터 처음 나오는 :의 위치를 찾기
length = int(s[i:colon]) # int(s[i:colon]) : 앞의 숫자를 문자열 길이로 변환
i = colon + 1 # 문자열 시작 위치로 이동
decoded.append(s[i:i+length]) # s[i:i+length : 길이만큼 문자열을 잘라서 저장함.
i += length # 다음 문자열의 시작 위치로 이동
return decoded
43 changes: 43 additions & 0 deletions group-anagrams/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'''
이 문제는 정렬된 문자열을 키(key)로 사용해 같은 애너그램끼리 그룹화함
즉, 각 단어를 정렬했을 때 같은 결과가 나오면 같은 그룹임

["eat","tea","tan","ate","nat","bat"]
단어 | 정렬 결과 | 키 | groups 딕셔너리 변화
eat aet aet {'aet': ['eat']}
tea aet aet {'aet': ['eat', 'tea']}
tan ant ant {'aet': [...], 'ant': ['tan']}
ate aet aet {'aet': ['eat','tea','ate'], ...}
nat ant ant {'ant': ['tan','nat'], ...}
bat abt abt {'abt': ['bat'], ...}

'''
class Solution:
def groupAnagrams(self, strs: List[str]):
groups = defaultdict(list) # defaultdict : 키가 없어도 자동으로 빈 리스트 생성

for word in strs: # 각 단어를 하나씩 확인
sorted_word = ''.join(sorted(word)) # 단어를 정렬해 키 생성 (예: "tea" → "aet")
groups[sorted_word].append(word) # 같은 키를 가진 단어를 그룹에 추가

return list(groups.values()) # 그룹들을 리스트로 변환해 반환

'''
시간 복잡도: O(n × klogk) + O(n) = O(n × klogk)
n: 단어 개수
k: 단어의 최대 길이
각 단어 정렬에 O(k log k) 시간 소요 (ex. 5글자 → 5 log 5 ≈ 11)
단어 정렬: 각 단어를 정렬하는 데 O(k log k) 시간이 소요됩니다.
파이썬의 sorted() 함수는 내부적으로 Timsort 알고리즘을 사용하며,
이의 시간 복잡도는 평균적으로 O(k log k)임
n개의 단어 처리: 모든 단어에 대해 정렬을 수행하므로 n × O(k log k) = O(n × k log k)610.
딕셔너리 연산: 정렬된 키를 기반으로 단어를 그룹화하는 작업은 O(1) 시간에 이루어지며,
전체적으로 O(n) 시간이 추가됩니다

공간 복잡도: O(n × k) + O(n × k) = O(n × k)
정렬된 키 저장: 각 단어를 정렬한 결과를 문자열로 저장함
단어 길이가 k일 때, 정렬된 문자열 저장에 O(k) 공간이 필요함
n개의 단어에 대해 총 O(n × k) 공간이 사용됨
딕셔너리 값 저장: 원본 단어를 그룹별로 저장함. 모든 단어를 저장해야 하므로

'''
78 changes: 78 additions & 0 deletions implement-trie-prefix-tree/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'''
이 문제는 문자열을 효율적으로 저장하고 검색하는 트리 구조임 (예 : 자동완성 기능)
주요 기능 : 단어 추가(insert), 단어 검색(search), 접두사 확인(startsWith)

Example 1.의 단계별 동작

메서드 호출 순서 리스트 인자 리스트 실제 호출 예시
"Trie" [] Trie() -> 빈 트라이를 만듬. 루트 노드가 생성되고, 아무 문자도 저장되어 있지 않음
"insert" ["apple"] insert("apple") -> 'a' 노드가 없으면 새로 만들고 이동
-> 'p' 노드가 없으면 새로 만들고 이동
-> 또 'p' 노드가 없으면 새로 만들고 이동
-> 'l' 노드가 없으면 새로 만들고 이동
-> 'e' 노드가 없으면 새로 만들고 이동
-> 마지막 'e' 노드에 is_end = True 표시 (여기까지가 "apple"이라는 단어임을 뜻함)

"search" ["apple"] search("apple") -> 루트부터 'a' → 'p' → 'p' → 'l' → 'e' 노드까지 차례로 이동
-> 마지막 'e' 노드가 is_end = True 이므로 True 반환

"search" ["app"] search("app") -> 루트부터 'a' → 'p' → 'p' 노드까지 이동
-> 'p' 노드의 is_end 값이 False (아직 "app"이라는 단어가 완성되지 않았음)
-> 따라서 False 반환
"startsWith" ["app"] startsWith("app") -> 루트부터 'a' → 'p' → 'p' 노드까지 이동
-> 노드가 존재하므로 True 반환 (접두사는 존재함)

"insert" ["app"] insert("app") -> 루트부터 'a' → 'p' → 'p' 노드까지 이미 존재하므로 새 노드 생성 안 함
-> 마지막 'p' 노드에 is_end = True 표시 (이제 "app"도 완성된 단어임)
-> "app"은 처음에 is_end=False였기 때문에 검색 시 False가 나왔고, 삽입 후 True가 됨

"search" ["app"] search("app") -> 루트부터 'a' → 'p' → 'p' 노드까지 이동
-> 마지막 'p' 노드가 is_end = True 이므로 True 반환

<트라이 구조>

루트
└─ 'a'
└─ 'p'
└─ 'p' (is_end=True) ← "app"
└─ 'l'
└─ 'e' (is_end=True) ← "apple"


'''

class TrieNode: # 트라이의 각 노드를 표현하는 클래스
def __init__(self):
self.children = {} # 현재 노드의 자식들을 {문자:노드} 형태로 저장
self.is_end = False # 현재 노드가 단어의 끝인지 표시(기본값 False)

class Trie:
def __init__(self):
self.root = TrieNode() # 트라이의 시작점인 빈 루트 노드 생성

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_end = True # 단어의 마지막 문자 노드에 끝 표시

def search(self, word: str) -> bool:
node = self.root
for char in word: # 단어의 각 문자를 따라 이동
if char not in node.children: # 문자가 없으면 단어 존재 X -> False
return False
node = node.children[char]
return node.is_end # 모든 문자를 통과했다면, 마지막 노드가 단어 끝인지 확인


def startsWith(self, prefix: str) -> bool:
node = self.root
for char in prefix: # 접두사의 각 문자를 따라 이동
if char not in node.children: # 문자 없으면 접두사 존재 X -> False
return False # 모든 문자가 존재 -> 접두사 O
node = node.children[char]
return True # 7. 접두사 존재


35 changes: 35 additions & 0 deletions word-break/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'''
주어진 문자열 s를 공백으로 구분된 단어 시퀀스로 분할할 수 있는지 확인하는 문제임
이때 모든 단어는 주어진 사전(wordDict)에 포함되어야 함

Example 1.의 예를 들면
dp = [True, False, False, False, False, False, False, False, False]
인덱스: 0 1 2 3 4 5 6 7 8
의미: "" "l" "le" "lee" "leet" "leetc" "leetco" "leetcod" "leetcode"
가능한 단어 길이: l=1~4 l=1~4 l=1~4 l=4 l=1~4 l=1~4 l=1~4 l=4

dp[i]가 True가 되는 조건:
dp[i-l]이 True이고, s[i-l:i]가 사전에 있는 단어일 때
예시에서 i=4와 i=8에서 조건이 충족되어 True로 변경됨

'''

class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
word_set = set(wordDict) # 단어 검색을 빠르게 하기 위해 집합으로 변환
if not word_set: # 사전이 비어있으면 항상 False
return False

max_len = max(len(word) for word in word_set) # 사전에서 가장 긴 단어의 길이 구분
dp = [False] * (len(s) + 1) # dp[i]는 문자열 s의 첫 i글자가 분할 가능한지 여부를 저장
dp[0] = True # 빈 문자열은 항상 분할 가능하므로 True

for i in range(1, len(s) + 1): # 문자열의 모든 위치를 순회함
for l in range(1, max_len + 1): # 단어 길이를 1부터 max_len까지 확인함
if i < l: # 현재 위치보다 단어 길이가 길면 패스
break
if dp[i - l] and s[i-l:i] in word_set: # 이전 위치가 True이고, 현재 부분 문자열이 사전에 있으면 True로 표시함
dp[i] = True
break # 하나라도 성공하면 더 확인할 필요 없음

return dp[-1] # 최종 결과 반환