Skip to content

[KwonNayeon] Week 7 solutions #1470

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
13 changes: 12 additions & 1 deletion longest-substring-without-repeating-characters/KwonNayeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,34 @@
3. 중복 문자를 만나면 윈도우의 시작점(current_start)을 중복 문자 다음 위치로 이동
4. 매 단계에서 현재 윈도우의 길이를 계산하고 최대 길이 갱신
5. 최종적으로 가장 긴 중복 없는 부분 문자열의 길이 반환

노트:
- 처음에 어려웠던 부분: 딕셔너리를 사용한다고 해서, 지금까지 캐릭터가 등장한 횟수를 저장해야 한다고 생각함
- 실제로는 각 문자의 마지막 등장 위치(인덱스)를 저장하는 용도임
"""

# e.g., s = "abca"
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
seen = {}
seen = {} # 각 문자가 마지막으로 등장한 인덱스를 저장하는 딕셔너리
current_start = 0
max_length = 0

for i in range(len(s)):
char = s[i]

# 현재 문자가 이미 등장했고, 그 위치가 현재 고려 중인 부분 문자열 내에 있는 경우
if char in seen and seen[char] >= current_start:

# 중복 발생, 윈도우 시작점을 이전 등장 위치 다음으로 이동
current_start = seen[char] + 1

seen[char] = i

# 현재 부분 문자열의 길이 계산 (현재 인덱스 - 시작 인덱스 + 1)
current_length = i - current_start + 1

# 최대 길이 업데이트
max_length = max(current_length, max_length)

return max_length
15 changes: 10 additions & 5 deletions number-of-islands/KwonNayeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
- 최악의 경우(모든 셀이 '1'일 때) m * n 만큼의 재귀 호출 스택 사용

풀이 방법:
1. 2중 for문으로 그리드의 모든 셀을 순회
2. '1'을 발견하면 DFS로 연결된 모든 땅을 방문하고 '0'으로 표시
3. '1'을 발견할 때마다 islands 카운트를 1씩 증가
1. numIslands 메서드: 그리드를 순회하며 '1'을 찾고 섬의 개수를 세는 역할
2. visit_island 메서드: 하나의 섬을 완전히 탐색하고 방문한 땅을 '0'으로 바꾸는 역할
3. 두 함수가 작동하는 방식:
- numIslands는 섬의 시작점('1')을 찾아 카운트를 증가시킴
- visit_island는 찾은 섬을 완전히 탐색하고 방문 표시('0'으로 변경)
"""

class Solution:
Expand All @@ -29,14 +31,17 @@ def numIslands(self, grid: List[List[str]]) -> int:
return islands

def visit_island(self, grid, i, j):
# base case: 위치가 범위를 벗어나거나 '1'이 아니면 함수를 종료
if (i < 0 or i >= len(grid) or
j < 0 or j >= len(grid[0]) or
grid[i][j] != '1'):
return

# 방문한 땅을 '0'으로 표시하여 다시 방문하지 않도록 함
grid[i][j] = '0'

self.visit_island(grid, i+1, j) # 위
self.visit_island(grid, i-1, j) # 아래
# 네 방향(아래, 위, 오른쪽, 왼쪽)으로 DFS 재귀 호출
self.visit_island(grid, i+1, j) # 아래
self.visit_island(grid, i-1, j) # 위
self.visit_island(grid, i, j+1) # 오른쪽
self.visit_island(grid, i, j-1) # 왼쪽
52 changes: 50 additions & 2 deletions reverse-linked-list/KwonNayeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
- The number of nodes in the list is the range [0, 5000]
- -5000 <= Node.val <= 5000

<Solution 1>

Time Complexity: O(n)
- n은 linked list의 노드 수
- 리스트를 한 번 순회하면서 각 노드를 한 번씩만 방문하기 때문
- 리스트를 한 번 순회하면서 각 노드를 한 번씩만 방문

Space Complexity: O(1)
- 추가 공간으로 prev, curr, temp 세 개의 포인터만 사용
- 입력 크기와 관계없이 일정한 추가 공간만 사용
- 입력 크기와 관계없이 일정한 추가 공간만 사용함

풀이 방법:
1. 세 개의 포인터를 사용하여 리스트를 순회하면서 뒤집기
Expand Down Expand Up @@ -45,3 +47,49 @@ def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
curr = temp

return prev

"""
<Solution 2>

Time Complexity: O(n)
- 스택에 모든 노드를 넣을 때/뺄 때 각 O(n) 시간을 소모함

Space Complexity: O(n)
- 스택에 모든 노드를 넣었다가 빼야 하므로

풀이방법:
- 스택의 LIFO 특성을 활용

노트:
- 풀이 1보다 공간 복잡도가 올라가지만, 더 이해하기 쉬운 풀이
"""

class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 모든 노드를 순서대로 저장할 빈 리스트 생성
nodes = []

# 현재 노드를 헤드로 초기화
node = head

# 링크드 리스트의 모든 노드를 순회하며 리스트에 저장
while node:
nodes.append(node)
node = node.next

# 새 링크드 리스트의 시작점이 될 더미 노드
dummy = ListNode(-1)

# 새 리스트를 만들기 위한 포인터 초기화
node = dummy

# nodes 리스트에서 역순으로 노드를 꺼내서 새 리스트 구성
while nodes:
node.next = nodes.pop() # 리스트의 마지막 노드를 꺼내서 연결
node = node.next # 노드 이동

# 마지막 노드의 next를 None으로 설정하여 리스트 종료
node.next = None

# 더미 노드의 next = 뒤집힌 리스트의 헤드
return dummy.next
35 changes: 20 additions & 15 deletions set-matrix-zeroes/KwonNayeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@
- 1 <= m, n <= 200
- -2^31 <= matrix[i][j] <= 2^31 - 1

Time Complexity: O(m*n)
- m은 행, n은 열을 의미
- 0 찾기: O(m*n)
- 행과 열 변환: O(m*n)
<Solution 1>

Space Complexity: O(m*n)
- zeros 배열이 최대 m*n 크기까지 저장 가능
Time Complexity: O(m * n)
- m은 행, n은 열을 의미함
- 0 찾기: O(m * n)
- 행과 열 변환: O(m * n)

Space Complexity: O(m * n)
- zeros 배열이 최대 m * n 크기까지 저장 가능

풀이 방법:
1. 0 위치 저장
1. 0 위치를 탐색/저장
2. 저장된 0의 행과 열을 모두 0으로 변환
3. 주의점: 행렬 값 탐색과 변경을 동시에 수행하면 원래 어떤 값이 0이었는지 구분하기 어려워짐
"""

class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Expand All @@ -32,17 +32,21 @@ def setZeroes(self, matrix: List[List[int]]) -> None:
zeros.append((r, c))

for r, c in zeros:
# 행(row)을 0으로 채움
for i in range(len(matrix[0])):
matrix[r][i] = 0
# 열(column)을 0으로 채움
for i in range(len(matrix)):
matrix[i][c] = 0

"""
Time Complexity: O(m*n)
- 행렬 순회: O(m*n)
- 행과 열 변환: O(m*n)
<Solution 2>

Time Complexity: O(m * n)
- 행렬 순회: O(m * n)
- 행과 열 변환: O(m * n)

Space Complexity: O(m+n)
Space Complexity: O(m + n)
- zero_rows: O(m)
- zero_cols: O(n)

Expand All @@ -51,7 +55,6 @@ def setZeroes(self, matrix: List[List[int]]) -> None:
2. 행과 열 정보를 분리 저장하여 메모리를 효율적으로 사용
3. 행과 열을 독립적으로 처리하여 불필요한 반복 연산 제거
"""

class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Expand All @@ -75,7 +78,9 @@ def setZeroes(self, matrix: List[List[int]]) -> None:
matrix[i][c] = 0

"""
Time Complexity: O(m*n)
<Solution 3>

Time Complexity: O(m * n)

Space Complexity: O(1)
- 추가적인 메모리를 사용하지 않고 첫 행과 열을 마커로 활용하여 해결
Expand Down
55 changes: 52 additions & 3 deletions unique-paths/KwonNayeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Constraints:
- 1 <= m, n <= 100

<Solution 1: 조합 활용>

Time Complexity: O(1)
- math.comb() 사용

Expand All @@ -13,11 +15,58 @@
1. (m-1)번 아래로, (n-1)번 오른쪽으로 가야함 -> 총 (m+n-2)번 이동
2. 결국 (m+n-2)번의 이동 중 (n-1)번의 오른쪽 이동을 선택하는 조합의 수
3. Combination 공식 적용: (m+n-2)C(n-1)

Further Consideration:
- DP로 풀어보기
- C(m+n-2, n-1) = (m+n-2)! / ((n-1)! × (m-1)!)
- 풀이가 간결함, 하지만 큰 수에서 오버플로우 가능성 있음
"""
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
from math import comb
return comb(m+n-2, n-1)

"""
<Solution 2: DP 활용>

Time Complexity: O(m * n)
- m은 row, n은 column의 길이
- 메모이제이션 활용, 모든 좌표에서 재귀 함수의 호출이 딱 한번만 일어나기 때문

Space Complexity: O(m * n)
- 함수의 호출 결과를 저장하는데 격자의 넓이에 비례하는 공간이 필요

풀이방법:
- 구현이 복잡함, 하지만 큰 수에서도 안정적임
- 재귀와 메모이제이션을 활용한 Top-down DP 접근법
- 현재 위치에서 목적지까지의 경로 수 = 아래로 이동 + 오른쪽으로 이동

2x3 그리드 예시:
각 위치에서 목적지까지 가는 경로 수:
(0,0)=3 (0,1)=2 (0,2)=1
(1,0)=1 (1,1)=1 (1,2)=1

위치 (0,0)에서 시작:
dfs(0,0) = dfs(1,0) + dfs(0,1) = 1 + 2 = 3

경로 3개:
1. 오른쪽, 오른쪽, 아래
2. 오른쪽, 아래, 오른쪽
3. 아래, 오른쪽, 오른쪽
"""
from functools import cache
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
@cache
def dfs(row, col):
if row == m - 1 and col == n - 1:
return 1

total = 0

if row + 1 < m:
total += dfs(row + 1, col)

if col + 1 < n:
total += dfs(row, col + 1)

return total

return dfs(0, 0)