Skip to content
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
78 changes: 78 additions & 0 deletions best-time-to-buy-and-sell-stock/unpo88.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
class Solution:
def maxProfit(self, prices: list[int]) -> int:
if not prices:
return 0

min_price = prices[0]
max_profit = 0

for price in prices[1:]:
max_profit = max(max_profit, price - min_price)
min_price = min(min_price, price)

return max_profit


"""
================================================================================
풀이 과정
================================================================================

[문제 이해]
────────────────────────────────────────────────────────────────────────────────
1. 주어진 배열 prices가 있고 prices[i]는 i날의 특정 주식 가격
2. 하나의 주식을 사기 위해 하루를 선택하고, 그 주식을 팔기 위해 미래의 다른 날을 선택
3. 이 거래에서 얻을 수 있는 최대 이익을 반환
4. 이익을 얻을 수 없다면 0을 반환

예시 1: [7, 1, 5, 3, 6, 4] → 5
2일에 사서 5일에 팔면 → 6 - 1 = 5가 나오면서 가장 큰 값

예시 2: [7, 6, 4, 3, 1] → 0
아무런 이익이 있는 날이 없음


[1차 시도] Brute Force 접근
────────────────────────────────────────────────────────────────────────────────
5. 모든 (구매일, 판매일) 쌍을 비교해서 최대 이익 찾기
6. 이중 반복문으로 구현

profit = 0
for i in range(len(prices)):
for j in range(i+1, len(prices)):
profit = max(profit, prices[j] - prices[i])
return profit

7. 시간복잡도: O(n²) → 시간 초과 발생!


[2차 시도] 최적화 방법 고민
────────────────────────────────────────────────────────────────────────────────
8. 그러면 다른 풀이 방식이 필요할 것 같은데
9. 조건을 정리:
- 이전 값이 다음 값보다 크면 버려도 된다
- 이전 값이 다음 값보다 작으면 해당 값을 기록한다
- 이미 기록된게 있으면 비교해본다

[7, 1, 5, 9, 3, 6, 4]
min을 추적하면서 현재가 - min 중 최대값을 찾으면 됨!


[최종 구현] Kadane's Algorithm 변형
────────────────────────────────────────────────────────────────────────────────
10. 한 번의 순회로 최소 구매가와 최대 이익을 동시에 추적
11. 각 시점에서:
- 현재가 - 최소가 = 지금 팔면 얻는 이익
- 최대 이익 갱신
- 최소가 갱신

min_price = prices[0]
max_profit = 0

for price in prices[1:]:
max_profit = max(max_profit, price - min_price)
min_price = min(min_price, price)

12. 시간복잡도: O(n) - 한 번의 순회
13. 공간복잡도: O(1) - 상수 공간만 사용
"""
58 changes: 58 additions & 0 deletions group-anagrams/unpo88.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
anagrams = defaultdict(list)
for s in strs:
anagrams["".join(sorted(s))].append(s)

return list(anagrams.values())


"""
================================================================================
풀이 과정
================================================================================

[문제 이해]
────────────────────────────────────────────────────────────────────────────────
1. 문자열 배열 strs가 주어지면 애너그램끼리 그룹화하여 반환
2. 애너그램: 같은 문자들을 재배열하여 만든 단어 (각 문자를 한 번씩만 사용)
3. 결과의 순서는 상관없음

예시 1: ["eat", "tea", "tan", "ate", "nat", "bat"]
→ [["bat"], ["nat", "tan"], ["ate", "eat", "tea"]]

예시 2: [""]
→ [[""]]

예시 3: ["a"]
→ [["a"]]


[접근 방법] 해시맵 + 정렬 키
────────────────────────────────────────────────────────────────────────────────
4. 애너그램의 특징: 정렬하면 같은 문자열이 됨
- "eat" → "aet"
- "tea" → "aet"
- "ate" → "aet"

5. 정렬된 문자열을 키로 사용하는 해시맵 구성
- 키: 정렬된 문자열
- 값: 해당 애너그램 그룹 리스트

6. 구현:
anagrams = defaultdict(list)
for s in strs:
anagrams["".join(sorted(s))].append(s)
return list(anagrams.values())


[복잡도 분석]
────────────────────────────────────────────────────────────────────────────────
7. 시간복잡도: O(n × k log k)
- n: 문자열 개수
- k: 가장 긴 문자열의 길이
- 각 문자열을 정렬하는데 O(k log k)

8. 공간복잡도: O(n × k)
- 해시맵에 모든 문자열 저장
"""
80 changes: 80 additions & 0 deletions implement-trie-prefix-tree/unpo88.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
class Trie:

def __init__(self):
self.children = {}
self.is_end = False

def insert(self, word: str) -> None:
node = self
for c in word:
if c not in node.children:
node.children[c] = Trie()
node = node.children[c]
node.is_end = True

def search(self, word: str) -> bool:
node = self._find(word)
return node is not None and node.is_end

def startsWith(self, prefix: str) -> bool:
return self._find(prefix) is not None

def _find(self, s: str):
node = self
for c in s:
if c not in node.children:
return None
node = node.children[c]
return node


"""
================================================================================
풀이 과정
================================================================================

[문제 이해]
────────────────────────────────────────────────────────────────────────────────
1. Trie(트라이) 자료구조 구현
2. insert(word): 단어 삽입
3. search(word): 정확히 일치하는 단어 있는지
4. startsWith(prefix): prefix로 시작하는 단어 있는지


[1차 시도] 해시맵
────────────────────────────────────────────────────────────────────────────────
5. 처음에 해싱으로 풀었더니 통과는 했는데 startsWith에서 결국 전체 순회 필요
6. 시간복잡도 O(n × m) → 비효율적, 진짜 Trie로 풀어야겠다


[2차 시도] 진짜 Trie
────────────────────────────────────────────────────────────────────────────────
7. 각 노드가 children(자식 노드들)과 is_end(단어 끝 여부)를 가짐

8. 구조 예시: "app", "apple" 삽입 시

root
a
p
p (is_end: "app")
l
e (is_end: "apple")

9. insert: 문자 하나씩 따라가며 노드 생성, 마지막에 is_end = True
10. search: 문자 따라간 후 is_end가 True인지 확인
11. startsWith: 문자 따라갈 수 있으면 True


[복잡도 분석]
────────────────────────────────────────────────────────────────────────────────
12. 시간복잡도: O(m) - 모든 연산
- m: 단어/prefix 길이

13. 공간복잡도: O(n × m)
- n: 단어 개수, m: 평균 단어 길이
"""
60 changes: 60 additions & 0 deletions word-break/unpo88.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
word_set = set(wordDict)
dp = [False] * (len(s) + 1)
dp[0] = True

for i in range(1, len(s) + 1):
for j in range(i):
if dp[j] and s[j:i] in word_set:
dp[i] = True
break

return dp[len(s)]


"""
================================================================================
풀이 과정
================================================================================

[문제 이해]
────────────────────────────────────────────────────────────────────────────────
1. 문자열 s와 사전 wordDict가 주어짐
2. s를 사전에 있는 단어들로 분할할 수 있는지 판단

예시 1: s = "leetcode", wordDict = ["leet", "code"]
→ True ("leet" + "code")

예시 2: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
→ False (어떻게 나눠도 안 됨)


[접근 방법] DP
────────────────────────────────────────────────────────────────────────────────
3. dp[i] = s[0:i]를 사전 단어들로 분할 가능한가?

4. 점화식: dp[i] = dp[j] and s[j:i] in word_set (어떤 j에 대해)
- dp[j]가 True이고, s[j:i]가 사전에 있으면 dp[i] = True

5. 예시: s = "leetcode"

l e e t c o d e
0 1 2 3 4 5 6 7 8
T ? ? ? T ? ? ? T
│ │ │
빈문자열 "leet" "code"

dp[4] = dp[0] + "leet" → True
dp[8] = dp[4] + "code" → True


[복잡도 분석]
────────────────────────────────────────────────────────────────────────────────
6. 시간복잡도: O(n² × m)
- n: 문자열 길이
- m: 평균 단어 길이 (해시 비교)

7. 공간복잡도: O(n)
- dp 배열 크기
"""