Skip to content

[seungriyou] Week 07 Solutions #1466

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 15, 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
62 changes: 62 additions & 0 deletions longest-substring-without-repeating-characters/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# https://leetcode.com/problems/longest-substring-without-repeating-characters/

class Solution:
def lengthOfLongestSubstring_slow(self, s: str) -> int:
"""
[Complexity]
- TC: O(n)
- SC: O(n)

[Approach]
two pointer를 이동해가며 substring을 확인하는 과정에서, hash table을 이용하여 duplicate character를 O(1)에 lookup 할 수 있도록 한다.
p1, p2를 각각 왼쪽과 오른쪽 pointer라고 할 때,
- substring의 맨 오른쪽 문자가 duplicate이 아니면, seen에 추가하고 맨 오른쪽 범위를 한 칸 늘린다. 그리고 max_len을 업데이트 한다.
- substring의 맨 오른쪽 문자가 duplicate이면, seen에서 맨 왼쪽 문자를 삭제하고 맨 왼쪽 범위를 한 칸 줄인다.
"""

p1 = p2 = max_len = 0
seen = set()

while p2 < len(s):
# p2가 중복이 아닌 문자를 가리키면, seen에 추가하고 p2 한 칸 옮기기
if s[p2] not in seen:
seen.add(s[p2])
p2 += 1
# max_len 업데이트
max_len = max(max_len, p2 - p1)

# p2가 중복인 문자를 가리키면, seen에서 s[p1] 삭제하고 p1 한 칸 옮기기
else:
seen.remove(s[p1])
p1 += 1

return max_len

def lengthOfLongestSubstring(self, s: str) -> int:
"""
[Complexity]
- TC: O(n)
- SC: O(n)

[Approach]
lengthOfLongestSubstring_slow()의 경우, p2가 중복인 문자를 가리킬 때 p1을 한 칸씩만 이동한다.
하지만 해당 문자가 가장 마지막으로 나온 인덱스를 기록해둔다면, 바로 그 다음 인덱스로 p1을 이동시킬 수 있다.
이러한 인덱스를 lookup 하는 행위도 hash table을 이용해 O(1)으로 최적화 할 수 있다.
"""

p1 = max_len = 0
seen = dict()

for p2, right in enumerate(s):
# right 문자가 p1 ~ p2 내 범위에서 이미 등장했다면, p1을 right 문자가 가장 마지막으로 등장한 인덱스의 다음 위치로 이동
if seen.get(right, -1) >= p1:
p1 = seen[right] + 1

# 그렇지 않다면, p1을 그대로 두고 max_len 업데이트
else:
max_len = max(max_len, p2 - p1 + 1)

# right를 seen에 기록
seen[right] = p2

return max_len
76 changes: 76 additions & 0 deletions number-of-islands/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# https://leetcode.com/problems/number-of-islands/

from typing import List

class Solution:
def numIslands_bfs(self, grid: List[List[str]]) -> int:
"""
[Complexity]
- TC: O(m * n)
- SC: O(m * n)

[Approach]
BFS를 사용한다.
"""
from collections import deque

dr, dc = [-1, 1, 0, 0], [0, 0, -1, 1]
m, n = len(grid), len(grid[0])
res = 0

def bfs(r, c):
q = deque([(r, c)])
grid[r][c] = "0"

while q:
r, c = q.popleft()

for i in range(4):
nr, nc = r + dr[i], c + dc[i]
# nr, nc가 (1) 범위 내이면서 (2) 아직 방문하지 않은 곳이라면
if 0 <= nr < m and 0 <= nc < n and grid[nr][nc] == "1":
q.append((nr, nc))
grid[nr][nc] = "0"

for r in range(m):
for c in range(n):
if grid[r][c] == "1":
bfs(r, c)
res += 1

return res

def numIslands(self, grid: List[List[str]]) -> int:
"""
[Complexity]
- TC: O(m * n)
- SC: O(m * n) (call stack)

[Approach]
DFS를 사용한다.
"""
dr, dc = [-1, 1, 0, 0], [0, 0, -1, 1]
m, n = len(grid), len(grid[0])
res = 0

def dfs(r, c):
# base condition
if not (0 <= r < m and 0 <= c < n) or grid[r][c] == "0":
return

# visited 처리
grid[r][c] = "0"

# recur
dfs(r - 1, c)
dfs(r + 1, c)
dfs(r, c - 1)
dfs(r, c + 1)

for r in range(m):
for c in range(n):
if grid[r][c] == "1":
dfs(r, c)
res += 1

return res
70 changes: 70 additions & 0 deletions reverse-linked-list/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# https://leetcode.com/problems/reverse-linked-list/

from typing import Optional

# Definition for singly-linked list.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next

class Solution:
def reverseList_iter(self, head: Optional[ListNode]) -> Optional[ListNode]:
"""
[Complexity]
- TC: O(n)
- SC: O(1)

[Approach]
linked-list를 reverse 할 때의 핵심은
(1) p1 -> p2를 p1 <- p2로 바꾸고
(2) p1, p2를 모두 한 칸 앞으로 전진
하는 것이다.
이때, (1)에서 p2.next = p1로 바꾼다면, p2를 한 칸 앞으로 전진할 수 없기 때문에 먼저 p2.next(= p3)를 기록해둬야 한다.
이를 iterative 하게 구현하면 while문을 이용할 수 있다.
"""
p1, p2 = None, head

while p2:
# 1. p3 얻어놓기
p3 = p2.next

# 2. p1 <- p2 전환
p2.next = p1

# 3. p1 -> p2, p2 -> p3 이동
p1, p2 = p2, p3

return p1

def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
"""
[Complexity]
- TC: O(n)
- SC: O(n) (call stack)

[Approach]
linked-list를 reverse 할 때의 핵심은
(1) p1 -> p2를 p1 <- p2로 바꾸고
(2) p1, p2를 모두 한 칸 앞으로 전진
하는 것이다.
이때, (1)에서 p2.next = p1로 바꾼다면, p2를 한 칸 앞으로 전진할 수 없기 때문에 먼저 p2.next(= p3)를 기록해둬야 한다.
이를 recursive 하게 구현하려면 다음과 같이 base condition으로 종료조건을 추가해주면 된다.
"""

def reverse_ll(p1, p2):
# base condition
if not p2:
return p1

# recur
# 1. p3 얻어놓기
p3 = p2.next

# 2. p1 <- p2 전환
p2.next = p1

# 3. p1 -> p2, p2 -> p3 이동
return reverse_ll(p2, p3)

return reverse_ll(None, head)
91 changes: 91 additions & 0 deletions set-matrix-zeroes/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# https://leetcode.com/problems/set-matrix-zeroes/

from typing import List

class Solution:
def setZeroes_slow(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.

[Complexity]
- TC: O(m * n * (m + n)) -> too slow...
- SC: O(1)

[Approach]
다음의 두 단계를 inplace로 수행한다.
1. 모든 cell을 순회하며, 0을 발견하면 해당 row와 column의 값을 (0인 cell을 제외하고) 모두 #로 바꾼다.
2. 다시 모든 cell을 순회하며, #을 모두 0으로 바꾼다.
"""

m, n = len(matrix), len(matrix[0])

def set_row_col(r, c):
for _r in range(m):
# 0이 아닌 경우에만 "#"으로 바꿔야 함 **
if matrix[_r][c] != 0:
matrix[_r][c] = "#"

for _c in range(n):
if matrix[r][_c] != 0:
matrix[r][_c] = "#"

# 1. 0을 발견하면 해당 row & column을 "#"으로 바꾸기
for r in range(m):
for c in range(n):
if matrix[r][c] == 0:
# row & column을 "#"으로 바꾸기
set_row_col(r, c)

# 2. "#"을 모두 0으로 바꾸기
for r in range(m):
for c in range(n):
if matrix[r][c] == "#":
matrix[r][c] = 0

def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.

[Complexity]
- TC: O(m * n)
- SC: O(1)

[Approach]
matrix의 첫 번째 row와 column을 flag 기록 용도로 사용한다면, space를 O(1)로 유지하면서 time도 최적화할 수 있다.
- 첫 번째 row: 각 column에 0이 있었다면, 해당 column의 칸에 0으로 기록
- 첫 번째 column: 각 row에 0이 있었다면, 해당 row의 칸에 0으로 기록
이렇게 첫 번째 row와 column을 flag 기록용으로 쓰기 전에, 해당 row와 column에 0이 있었는지 여부를 미리 확인해야 함에 유의한다.
전체 흐름은 다음과 같다:
1. 첫 번째 row와 column에 0이 존재하는지 여부 확인
2. 두 번째 row와 column 부터 0이 존재하는 칸에 대해 첫 번째 row와 column에 flag 기록
3. 첫 번째 row와 column의 flag 값을 기반으로, 0 채우기
4. 1번에서 구해둔 값으로도 0 채우기
"""

m, n = len(matrix), len(matrix[0])

# 1. 첫 번째 row와 column에 0이 존재하는지 여부 확인
has_zero_in_first_row = any(matrix[0][_c] == 0 for _c in range(n))
has_zero_in_first_col = any(matrix[_r][0] == 0 for _r in range(m))

# 2. 두 번째 row와 column 부터 0이 존재하는 칸에 대해 첫 번째 row와 column에 flag 기록
for r in range(1, m):
for c in range(1, n):
if matrix[r][c] == 0:
matrix[0][c] = 0
matrix[r][0] = 0

# 3. 첫 번째 row와 column의 flag 값을 기반으로, 0 채우기
for r in range(1, m):
for c in range(1, n):
if matrix[0][c] == 0 or matrix[r][0] == 0:
matrix[r][c] = 0

# 4. 1번에서 구해둔 값으로도 0 채우기
if has_zero_in_first_row:
for _c in range(n):
matrix[0][_c] = 0

if has_zero_in_first_col:
for _r in range(m):
matrix[_r][0] = 0
49 changes: 49 additions & 0 deletions unique-paths/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# https://leetcode.com/problems/unique-paths/

class Solution:
def uniquePaths_2d(self, m: int, n: int) -> int:
"""
[Complexity]
- TC: O(m * n)
- SC: O(m * n)

[Approach]
로봇이 down or right로만 이동할 수 있으므로, 어떤 칸에 도달할 수 있는 unique path의 개수는 up 칸과 left 칸까지의 unique path의 개수를 더한 값이 된다.
따라서 2D DP로 풀 수 있으며, dp table은 다음과 같다.
dp[r][c] = (up 칸까지의 unique path) + (left 칸까지의 unique path)
= dp[r - 1][c] + dp[r][c - 1]
이때, dp table의 첫 번째 row와 column에 있는 칸들은 모두 도달할 수 있는 unique path의 개수가 1이므로 초기화해준다.
"""
# 첫 번째 row & column은 1로 초기화
dp = [[1 for _ in range(n)] for _ in range(m)]

# 두 번째 row & column 부터 순회
for r in range(1, m):
for c in range(1, n):
dp[r][c] = dp[r - 1][c] + dp[r][c - 1]

return dp[m - 1][n - 1]

def uniquePaths(self, m: int, n: int) -> int:
"""
[Complexity]
- TC: O(m * n)
- SC: O(n) (1D DP로 space optimization)

[Approach]
2D DP에서 dp[r][c]를 구할 때 dp[r - 1][c]과 dp[r][c - 1]만 사용하므로,
rolling array 기법을 이용해 1D DP로 space optimization이 가능하다.
따라서 길이가 n인 1D dp list를 이용해
- c(= 1 ~ n - 1)를 순회하며 dp[c] += dp[c - 1]로 업데이트하는 것을 (2D DP에서의 dp[r - 1][c]가 dp[c] 값이므로!)
- row 개수인 m 번 반복 (단, 첫 번째 row를 1로 초기화 한 효과를 얻기 위해 dp를 1로 초기화 하고 m - 1번 반복)
하면 된다.
"""

# 첫 번째 row는 1로 초기화 한 효과
dp = [1] * n

for _ in range(1, m): # m - 1 번 반복
for c in range(1, n): # 첫 번째 col은 1로 초기화 한 효과
dp[c] += dp[c - 1]

return dp[n - 1]