Skip to content

[KwonNayeon] Week 9 Solutions #1523

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 9 commits into from
May 30, 2025
46 changes: 27 additions & 19 deletions linked-list-cycle/KwonNayeon.py
Copy link
Contributor

Choose a reason for hiding this comment

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

최적화된 알고리즘을 찾기 위해 여러 개의 solution을 시도하시는 부분이 인상 깊었습니다. 꼼꼼하게 풀이방법을 적어주시는 부분도 마찬가지로 인상 깊어요.

Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,24 @@
- -10^5 <= Node.val <= 10^5
- pos is -1 or a valid index in the linked-list

Time Complexity:
- Solution 1: O(n)
- Solution 2: O(n)

Space Complexity:
- Solution 1: O(n) - visited set에 모든 노드를 저장할 수 있음
- Solution 2: O(1) - 추가 메모리 사용하지 않음

풀이방법:
1. 처음엔 직관적인 방법으로 해결, 한 번 마주친 노드를 다시 만나는지를 체크하는 방식
2. slow, fast 두 개의 노드를 활용, 만약 cycle이 존재하는 경우 fast가 slow와 언젠가 만나게 됨,
만약 cycle이 없다면 둘은 만나지 않음
"""

<Solution 1>

Time Complexity: O(n)
- while 루프를 최대 n번 실행 (노드의 개수만큼)

Space Complexity: O(n)
- visited set에 최대 n개의 노드 저장함
- set() 조회/삽입에 O(1)

풀이 방법:
- 한 번 마주친 노드를 다시 만나는지를 체크하는 방식
"""
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None

# Solution 1: 한 번 마주친 노드를 다시 만나는지를 체크
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
visited = set()
Expand All @@ -38,14 +35,26 @@ def hasCycle(self, head: Optional[ListNode]) -> bool:

return False

# Solution 2: 두 개의 포인터 이용
"""
<Solution 2>

Time Complexity: O(n)
- while 루프를 최대 n번 실행 (노드의 개수만큼)

Space Complexity: O(1)
- 투 포인터 이외의 추가 메모리 사용하지 않음

풀이 방법:
- 투 포인터 방식 (slow, fast)
- 만약 cycle이 있다면, fast가 slow와 언젠가 만나게 됨
- cycle이 없다면 둘은 만나지 않음
"""
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
if not head:
return False

slow = head
fast = head
slow = fast = head

while fast and fast.next:
slow = slow.next
Expand All @@ -55,4 +64,3 @@ def hasCycle(self, head: Optional[ListNode]) -> bool:
return True

return False

20 changes: 9 additions & 11 deletions maximum-product-subarray/KwonNayeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@
- -10 <= nums[i] <= 10
- The product of any subarray of nums is guaranteed to fit in a 32-bit integer.

Time Complexity:
- O(n): 배열을 한 번만 순회하면서 각 위치에서 상수 시간 연산만 수행
Time Complexity: O(n)
- 배열을 한 번만 순회하고, 각 위치에서 상수 시간 연산(max, min, 곱셈)만 수행

Space Complexity:
- O(1): 고정된 추가 변수만 사용 (curr_max, curr_min, ...)
Space Complexity: O(1)
- 입력 크기와 무관하게 고정된 변수(curr_max, curr_min, result, temp)만 사용

풀이방법:
1. DP로 각 위치에서 가능한 최대곱과 최소곱을 동시에 추적함
2. 각 위치에서 세 개의 선택지 존재: 새로 시작 vs 이전 최대곱과 곱하기 vs 이전 최소곱과 곱하기
3. 최소곱이 필요한 이유: 나중에 음수를 만났을 때 최대값이 될 수 있기 때문
1. DP로 각 위치에서 가능한 최대값과 최소값을 함께 업데이트함
2. 각 위치에서 세 개의 선택지 존재: 새로 시작 vs 이전 최대값과 곱하기 vs 이전 최소값과 곱하기
3. 최소값을 계속 업데이트 하는 이유: 음수*음수 = 새로운 최대값, curr_max 업데이트할 때 쓰임 (예시: nums = [2, 3, -2, 4, -1])
4. 매 단계마다 result 업데이트

고려사항:
1. 값이 0인 경우 새로 시작해야 함
2. temp 변수: curr_max를 업데이트하면 curr_min 계산 시 원래 값이 필요함
1. 값이 0인 경우 새로 시작해야 함
2. temp 변수: curr_min 계산 시 curr_max를 업데이트하기 전의 값이 필요함
"""
class Solution:
def maxProduct(self, nums: List[int]) -> int:
Expand All @@ -33,5 +33,3 @@ def maxProduct(self, nums: List[int]) -> int:
result = max(result, curr_max)

return result


10 changes: 7 additions & 3 deletions minimum-window-substring/KwonNayeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@
3. 최소 길이의 윈도우 반환

메모:
- 풀이 방법을 보고 익힘
- 해시맵과 슬라이딩 윈도우 관련 다른 문제들을 풀고 이 문제 스스로 풀어보기
- 관련 문제:
- Two Sum (딕셔너리)
- Ransom Note (Counter)
- Longest Substring Without Repeating (슬라이딩 윈도우)
- Permutation in String (슬라이딩 윈도우 + Counter)
- Find All Anagrams in a String (슬라이딩 윈도우 + Counter)
- 4, 5번 문제 먼저 풀어보기
"""
class Solution:
def minWindow(self, s: str, t: str) -> str:
Expand All @@ -46,4 +51,3 @@ def minWindow(self, s: str, t: str) -> str:
low += 1

return s[min_low : min_high + 1] if min_high < len(s) else ""

53 changes: 47 additions & 6 deletions pacific-atlantic-water-flow/KwonNayeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@
- 0 <= heights[r][c] <= 10^5

Time Complexity: O(m*n)
- 각 셀을 최대 한 번씩만 방문함
- 각 셀을 최대 한 번만 방문함

Space Complexity: O(m*n)
- visited sets(pacific, atlantic) 최대 m*n 크기
- 재귀 호출 스택도 최대 m*n 깊이 가능
- visited sets(pacific, atlantic) 최대 m*n 크기
- 재귀 호출 스택도 최대 m*n까지 가능

풀이방법:
1. Pacific(좌측상단)과 Atlantic(우측하단)의 경계에서 시작함
2. DFS로 현재 높이보다 높거나 같은 인접 셀로만 이동함 (물은 위 -> 아래로 흐르지만, 거꾸로 접근했으니까)
1. Pacific(좌측상단)과 Atlantic(우측하단)의 경계에서 시작
2. DFS로 현재 높이보다 높거나 같은 인접 셀로만 이동 (역방향 접근)
3. 각 바다에서 도달 가능한 셀들을 Set에 저장
4. 두 Set의 교집합이 정답 (양쪽 바다로 모두 흐를 수 있는 지점들)
4. 두 Set의 교집합으로 양쪽 바다 모두 도달 가능한 지점 반환
"""
# 원본 코드
class Solution:
def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
if not heights:
Expand Down Expand Up @@ -51,3 +52,43 @@ def dfs(r, c, visited):
dfs(r, cols-1, atlantic)

return list(pacific & atlantic)

# 개선된 코드
class Solution:
def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
rows, cols = len(heights), len(heights[0])

# 각 바다에서 도달 가능한 위치 저장
pacific = set()
atlantic = set()

def dfs(r, c, visited):
# 현재 위치 방문 처리
visited.add((r, c))

# 네 방향 탐색 (오른쪽, 위, 왼쪽, 아래)
for dr, dc in [(0, 1), (-1, 0), (0, -1), (1, 0)]:
new_r, new_c = r + dr, c + dc

# 경계 체크, 방문x 체크, 높이 체크
if (0 <= new_r < rows and
0 <= new_c < cols and
(new_r, new_c) not in visited and
heights[new_r][new_c] >= heights[r][c]):

dfs(new_r, new_c, visited)

# Pacific 경계에서 시작 (위 + 왼쪽)
for c in range(cols):
dfs(0, c, pacific)
for r in range(rows):
dfs(r, 0, pacific)

# Atlantic 경계에서 시작 (아래 + 오른쪽)
for c in range(cols):
dfs(rows-1, c, atlantic)
for r in range(rows):
dfs(r, cols-1, atlantic)

# 양쪽 바다 모두 도달 가능한 위치 반환
return [[r, c] for r, c in pacific & atlantic]
28 changes: 16 additions & 12 deletions sum-of-two-integers/KwonNayeon.py
Copy link
Contributor

Choose a reason for hiding this comment

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

음수 케이스 처리까지 함께 풀이에 적용해주셨네요. 주어진 문제의 조건을 꼼꼼하게 보시고 해결하시는 부분이 인상 깊어요.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- -1000 <= a, b <= 1000

Time Complexity: O(1)
- 비트 자리수, while loop 반복 횟수가 고정되어 있음

Space Complexity: O(1)
- 추가 공간을 사용하지 않고 입력받은 변수만 사용
Expand All @@ -11,25 +12,28 @@
1. XOR(^)연산을 통해 캐리를 제외한 각 자리의 합을 구함
2. AND(&)연산 후 왼쪽 시프트(<<)로 다음 자리로 올라갈 캐리를 구함
3. 캐리가 0이 될 때까지 1-2 과정을 반복
- 캐리는 유한한 비트 범위에서만 존재함
- 그러므로 계속 왼쪽으로 이동하다 결국 사라짐
- 캐리가 0이 되면 루프 종료
"""
# Solution 1: 이해하기 쉬운 버전
# Solution 1
class Solution:
def getSum(self, a: int, b: int) -> int:
while b:
current_sum = a ^ b

next_carry = (a & b) << 1

a = current_sum
b = next_carry
a, b = a ^ b, (a & b) << 1

return a

# Solution 2: 최적화 버전
# Solution 2: 음수 케이스 처리
class Solution:
def getSum(self, a: int, b: int) -> int:
while b:
a, b = a ^ b, (a & b) << 1
mask = 0xffffffff # 32비트 마스킹

return a
while b & mask:
# 캐리 없는 덧셈 + 32비트 제한, 캐리 계산 + 32비트 제한
a, b = (a ^ b) & mask, ((a & b) << 1) & mask

# a <= 0x7FFFFFFF: 양수일 때 그대로 반환(0x7FFFFFFF = 32비트 최대 양수)
# a > 0x7FFFFFFF: 음수일 때 Python 음수로 변환
return a if a <= 0x7FFFFFFF else a | (~mask)