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
79 changes: 79 additions & 0 deletions clone-graph/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""
문제의 핵심 포인트:
각 노드는 값과 이웃 노드들의 리스트를 가짐
원본 그래프와 완전히 독립적인 새로운 그래프 생성
모든 연결 관계를 그대로 복사
해결 방법:
DFS(깊이 우선 탐색) + 해시맵을 사용함:
해시맵으로 원본 노드와 복사본 노드의 매핑 관계 저장
DFS로 그래프를 순회하면서 각 노드를 복사
이미 복사된 노드는 해시맵에서 찾아서 재사용
Example 1의 경우
1 --- 2
| |
| |
4 --- 3
실행 과정:
노드 1 복사: clone_1 생성, visited = clone_1
노드 1의 이웃 2 복사: clone_2 생성, visited = clone_2
노드 2의 이웃 1 처리: 이미 visited에 있으므로 clone_1 반환
노드 2의 이웃 3 복사: clone_3 생성, visited = clone_3
노드 3의 이웃들 처리: clone_2, clone_4 연결
노드 1의 이웃 4 복사: clone_4 생성, visited = clone_4
모든 연결 관계 완성
Output: 원본과 동일한 구조의 완전히 새로운 그래프
시간 복잡도: O(V + E)
V: 노드(정점)의 개수, E: 간선의 개수
각 노드를 한 번씩 방문하고, 각 간선을 한 번씩 처리
DFS의 표준 시간 복잡도
공간 복잡도: O(V)
visited 해시맵이 모든 노드를 저장: O(V)
DFS 재귀 호출 스택의 최대 깊이: O(V)
복사된 그래프 자체도 O(V + E)의 공간 필요
"""

from typing import Optional
class Solution:
def cloneGraph(self, node: Optional['Node']) -> Optional['Node']:
# 빈 그래프인 경우 None 반환
if not node:
return None

# 원본 노드를 키로, 복사본 노드를 값으로 하는 해시맵 생성
# 중복 복사를 방지하고 순환 참조 문제 해결
visited = {}

# DFS 함수 정의
def dfs(original_node):
# 이미 복사된 노드라면 기존 복사본을 반환 (중복 복사 방지)
if original_node in visited:
return visited[original_node]

# 원본 노드의 값으로 새로운 노드 생성
# 이웃 리스트는 빈 리스트로 초기화
clone_node = Node(original_node.val, [])

# 해시맵에 원본-복사본 매핑 관계 저장
# 순환 참조 방지를 위해 이웃을 처리하기 전에 먼저 저장
visited[original_node] = clone_node

# 원본 노드의 모든 이웃들을 재귀적으로 복사
# 복사된 이웃 노드들을 현재 복사본의 이웃 리스트에 추가
for neighbor in original_node.neighbors:
# 이웃 노드를 복사하고 현재 노드의 이웃 리스트에 추가
clone_node.neighbors.append(dfs(neighbor))

# 완성된 복사본 노드 반환
return clone_node

# 주어진 노드부터 DFS 시작
return dfs(node)




43 changes: 43 additions & 0 deletions linked-list-cycle/taurus09318976.py
Copy link
Contributor

Choose a reason for hiding this comment

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

첫줄에서 edge 케이스를 먼저 확인하시는게 좋은것 같아요 :)

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'''
*문제의 의도 :
이 문제는 연결 리스트에 사이클이 있는지 판단하는 문제임.
사이클이란 어떤 노드에서 시작해서 next 포인터를 계속 이동시키다 다시 원래 노드로 돌아올 수 있는 경우를 말함

*해결방법 : 토끼와 거북이 알고리듬을 사용함
두 개의 포인터를 사용하는데, 느린 포인터(거북이)는 한 번에 한칸씩 이동,
빠른 포인터(토끼)는 한 번에 두 칸씩 이동, 사이클이 존재한다면 빠른 포인터가 느린 포인터를 따라잡게 됨.

*시간 복잡도와 공간 복잡도
시간 복잡도: O(n)
사이클이 없는 경우: 빠른 포인터가 n/2번 이동하여 끝에 도달하므로 O(n)
사이클이 있는 경우: 최악의 경우 사이클 내에서 느린 포인터와 빠른 포인터가 만날 때까지 사이클 길이만큼 추가로 이동하지만, 전체적으로는 여전히 O(n)

공간 복잡도: O(1)
두 개의 포인터 변수만 사용하므로 상수 공간만 필요
입력 크기에 관계없이 일정한 메모리 사용

'''

class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
# 연결 리스트가 비어 있거나 노드가 하나뿐이면 사이클이 불가능하므로 False 반환
if not head or not head.next:
return False

# 두 포인터를 모두 head에서 시작하도록 초기화
slow = head # 느린 포인터 (거북이)
fast = head # 빠른 포인터 (토끼)

# 빠른 포인터가 두 칸씩 이동하므로, fast와 fast.next 모두 존재하는지 확인
while fast and fast.next:
slow = slow.next # 느린 포인터는 한 칸씩 이동
fast = fast.next.next # 빠른 포인터는 두 칸씩 이동

# 두 포인터가 같은 노드를 가리키면 사이클이 존재함을 의미
if slow == fast:
return True

# 빠른 포인터가 끝에 도달하면 사이클이 없다는 의미
return False


80 changes: 80 additions & 0 deletions longest-repeating-character-replacement/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'''
문제의 의도
이 문제는 문자열에서 최대 k번의 문자 변경을 통해 같은 문자로만 이루어진 가장 긴 부분 문자열을 찾는 문제임.

핵심 포인트:
최대 k번까지 문자를 다른 문자로 바꿀 수 있음
바꾼 후 같은 문자로만 이루어진 부분 문자열의 최대 길이를 구해야 함
연속된 부분 문자열이어야 함

해결 방법
슬라이딩 윈도우(Sliding Window) + 해시맵 기법을 사용함:
윈도우 내에서 가장 빈도가 높은 문자를 찾기
나머지 문자들을 가장 빈도가 높은 문자로 바꾸는 데 필요한 변경 횟수 계산
변경 횟수가 k를 초과하면 윈도우 축소

실행 과정:
윈도우 "A": max_freq=1, 변경 필요=0 ≤ k=1
윈도우 "AA": max_freq=2, 변경 필요=0 ≤ k=1
윈도우 "AAB": max_freq=2, 변경 필요=1 ≤ k=1
윈도우 "AABA": max_freq=3, 변경 필요=1 ≤ k=1
윈도우 "AABAB": max_freq=3, 변경 필요=2 > k=1
윈도우 축소: "ABAB" → max_freq=2, 변경 필요=2 > k=1
계속 축소: "BAB" → max_freq=2, 변경 필요=1 ≤ k=1
Output: 4 (윈도우 "AABA"에서 B 하나를 A로 바꾸면 "AAAA")

핵심 아이디어:
윈도우 내에서 가장 많은 문자를 기준으로 하고,
나머지 문자들을 그 문자로 바꾸는 데 필요한 변경 횟수가 k 이하인지 확인.

시간 복잡도: O(n)
각 문자가 최대 2번 방문됨 (right 포인터로 1번, left 포인터로 1번)
해시맵 연산은 O(1)이므로 전체적으로 선형 시간

공간 복잡도: O(1)
영어 대문자만 사용하므로 해시맵 크기는 최대 26개로 상수
기타 변수들도 상수 공간
'''

class Solution:
def characterReplacement(self, s: str, k: int) -> int:
# 현재 윈도우 내 각 문자의 개수를 저장하는 해시맵
char_count = {}

# 슬라이딩 윈도우의 시작점
left = 0

# 윈도우 내에서 가장 빈도가 높은 문자의 개수
max_freq = 0

# 결과로 반환할 최대 길이
max_length = 0

# 오른쪽 포인터로 문자열을 순회하며 윈도우 확장
for right in range(len(s)):
# 현재 문자를 윈도우에 추가하고 빈도 증가
char = s[right]
char_count[char] = char_count.get(char, 0) + 1

# 현재 윈도우에서 가장 빈도가 높은 문자의 개수 업데이트
max_freq = max(max_freq, char_count[char])

# 현재 윈도우 크기
window_size = right - left + 1

# 변경해야 할 문자 개수 = 전체 윈도우 크기 - 가장 많은 문자 개수
# 이 값이 k보다 크면 윈도우가 유효하지 않음
if window_size - max_freq > k:
# 왼쪽 문자를 윈도우에서 제거하고 왼쪽 포인터 이동
left_char = s[left]
char_count[left_char] -= 1
left += 1

# 현재 윈도우 크기로 최대 길이 업데이트
max_length = max(max_length, right - left + 1)

return max_length




59 changes: 59 additions & 0 deletions maximum-product-subarray/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'''
문제의 의도
이 문제는 배열에서 연속된 부분 배열의 곱이 최대가 되는 값을 찾는 문제임.
음수가 있기 때문에 단순히 최대값만 추적하면 안 되고, 동시에 최소값도 추적해야 함.

핵심 포인트
음수 × 음수 = 양수 (큰 양수가 될 수 있음)
음수 × 양수 = 음수 (작아짐)
0이 있으면 곱이 0이 됨

해결 방법
동적 프로그래밍을 사용하되, 최대값과 최소값을 동시에 추적:

최대값: 양수 결과를 위해
최소값: 음수가 나중에 양수로 바뀔 가능성을 위해

시간 복잡도: O(n)
배열을 한 번만 순회하므로 O(n)
각 원소에서 상수 시간의 연산만 수행

공간 복잡도: O(1)
추가 배열을 사용하지 않음
몇 개의 변수만 사용하므로 상수 공간
'''

class Solution:
def maxProduct(self, nums: List[int]) -> int:
# 빈 배열 처리
if not nums:
return 0

# 첫 번째 원소로 모든 값들을 초기화
max_product = nums[0] # 전체 최대값
current_max = nums[0] # 현재 위치까지의 최대 곱
current_min = nums[0] # 현재 위치까지의 최소 곱

# 두 번째 원소부터 배열을 순회하며 현재 숫자를 저장
for i in range(1, len(nums)):
num = nums[i]

# 현재 숫자가 음수일 경우를 대비해 max와 min을 임시 저장
temp_max = current_max

# 새로운 최대값 계산: 최대값과 최소값 모두 아래의 3개 중에서 나올 수 있음
# num : 현재 숫자부터 새로 시작
# temp_max * num : 현재숫자 × 이전최대값
# current_min * num : 현재숫자 × 이전최소값(음수*음수=양수 가능성 떄문에)
current_max = max(num, temp_max * num, current_min * num)

# 새로운 최소값 계산: 나중에 음수와 곱해져서 큰 양수가 될 가능성을 위해
# 현재 숫자, 현재숫자×이전최대, 현재숫자×이전최소 중 최소
current_min = min(num, temp_max * num, current_min * num)

# 지금까지의 전체 최대값과 현재 최대값을 비교하여 업데이트
max_product = max(max_product, current_max)

return max_product


103 changes: 103 additions & 0 deletions minimum-window-substring/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
'''
문제의 의도
이 문제는 문자열 s에서 문자열 t의 모든 문자(중복 포함)를 포함하는 가장 짧은 부분 문자열을 찾는 문제임.

핵심 포인트:
1) t의 모든 문자가 포함되어야 함 (중복 개수도 맞아야 함)
2) 가장 짧은 길이의 부분 문자열을 찾아야 함
3) 연속된 부분 문자열이어야 함

해결 방법
슬라이딩 윈도우 + 투 포인터 기법을 사용함:
오른쪽 포인터로 윈도우를 확장하여 조건을 만족시킴
조건을 만족하면 왼쪽 포인터로 윈도우를 축소하여 최소 길이 찾기
해시맵으로 문자 개수를 효율적으로 관리

Example 1의 경우를 보면,

Input: s = "ADOBECODEBANC", t = "ABC"

실행 과정:
t_count: {'A':1, 'B':1, 'C':1}, required = 3
윈도우 확장: right 포인터로 "ADOBEC"까지 확장 → 조건 만족
윈도우 축소: left 포인터로 "ODEBANC" → "BANC"까지 축소
최종 결과: "BANC" (길이 4)

Output: "BANC"

시간 복잡도: O(|s| + |t|)
각 문자가 최대 2번 방문됨 (right 포인터로 1번, left 포인터로 1번)
t를 한 번 순회하여 해시맵 생성: O(|t|)
전체적으로 선형 시간 복잡도

공간 복잡도: O(|s| + |t|)
t_count 해시맵: O(|t|)
window_counts 해시맵: 최대 O(|s|)
기타 변수들: O(1)
'''

class Solution:
def minWindow(self, s: str, t: str) -> str:
# t가 빈 문자열이면 빈 문자열 반환
if not t:
return ""

# t의 각 문자 개수를 저장하는 해시맵
t_count = {}
for char in t:
t_count[char] = t_count.get(char, 0) + 1

# 조건을 만족하기 위해 필요한 고유 문자의 개수
required = len(t_count)

# 슬라이딩 윈도우의 왼쪽, 오른쪽 포인터와 조건을 만족하는 문자 개수 초기화
left = 0
right = 0

# 현재 윈도우에서 조건을 만족하는 문자의 개수
formed = 0

# 현재 윈도우의 문자 개수를 저장하는 해시맵
window_counts = {}

# 결과를 저장할 변수들
min_len = float('inf')
min_left = 0
min_right = 0

# 오른쪽 포인터로 윈도우 확장
while right < len(s):
# 오른쪽 문자를 윈도우에 추가
char = s[right]
window_counts[char] = window_counts.get(char, 0) + 1

# 현재 문자의 개수가 t에서 요구하는 개수와 같아지면 formed에 1을 더함
if char in t_count and window_counts[char] == t_count[char]:
formed += 1

# 모든 문자의 조건이 만족되면 왼쪽 포인터로 윈도우를 최소화 시도
while left <= right and formed == required:
char = s[left]

# 현재 윈도우가 지금까지의 최소 길이보다 작으면 결과 업데이트
if right - left + 1 < min_len:
min_len = right - left + 1
min_left = left
min_right = right

# 왼쪽 문자를 제거하고, 조건을 만족하지 않게 되면 formed에서 1을 뺌
window_counts[char] -= 1
if char in t_count and window_counts[char] < t_count[char]:
formed -= 1

# 왼쪽 포인터 이동
left += 1

# 오른쪽 포인터 이동
right += 1

# 결과 반환
return "" if min_len == float('inf') else s[min_left:min_right + 1]



Loading