Skip to content

Commit 7debfb4

Browse files
authored
Merge pull request #1466 from seungriyou/main
[seungriyou] Week 07 Solutions
2 parents 7f0d45c + 5f684be commit 7debfb4

File tree

5 files changed

+348
-0
lines changed

5 files changed

+348
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# https://leetcode.com/problems/longest-substring-without-repeating-characters/
2+
3+
class Solution:
4+
def lengthOfLongestSubstring_slow(self, s: str) -> int:
5+
"""
6+
[Complexity]
7+
- TC: O(n)
8+
- SC: O(n)
9+
10+
[Approach]
11+
two pointer를 이동해가며 substring을 확인하는 과정에서, hash table을 이용하여 duplicate character를 O(1)에 lookup 할 수 있도록 한다.
12+
p1, p2를 각각 왼쪽과 오른쪽 pointer라고 할 때,
13+
- substring의 맨 오른쪽 문자가 duplicate이 아니면, seen에 추가하고 맨 오른쪽 범위를 한 칸 늘린다. 그리고 max_len을 업데이트 한다.
14+
- substring의 맨 오른쪽 문자가 duplicate이면, seen에서 맨 왼쪽 문자를 삭제하고 맨 왼쪽 범위를 한 칸 줄인다.
15+
"""
16+
17+
p1 = p2 = max_len = 0
18+
seen = set()
19+
20+
while p2 < len(s):
21+
# p2가 중복이 아닌 문자를 가리키면, seen에 추가하고 p2 한 칸 옮기기
22+
if s[p2] not in seen:
23+
seen.add(s[p2])
24+
p2 += 1
25+
# max_len 업데이트
26+
max_len = max(max_len, p2 - p1)
27+
28+
# p2가 중복인 문자를 가리키면, seen에서 s[p1] 삭제하고 p1 한 칸 옮기기
29+
else:
30+
seen.remove(s[p1])
31+
p1 += 1
32+
33+
return max_len
34+
35+
def lengthOfLongestSubstring(self, s: str) -> int:
36+
"""
37+
[Complexity]
38+
- TC: O(n)
39+
- SC: O(n)
40+
41+
[Approach]
42+
lengthOfLongestSubstring_slow()의 경우, p2가 중복인 문자를 가리킬 때 p1을 한 칸씩만 이동한다.
43+
하지만 해당 문자가 가장 마지막으로 나온 인덱스를 기록해둔다면, 바로 그 다음 인덱스로 p1을 이동시킬 수 있다.
44+
이러한 인덱스를 lookup 하는 행위도 hash table을 이용해 O(1)으로 최적화 할 수 있다.
45+
"""
46+
47+
p1 = max_len = 0
48+
seen = dict()
49+
50+
for p2, right in enumerate(s):
51+
# right 문자가 p1 ~ p2 내 범위에서 이미 등장했다면, p1을 right 문자가 가장 마지막으로 등장한 인덱스의 다음 위치로 이동
52+
if seen.get(right, -1) >= p1:
53+
p1 = seen[right] + 1
54+
55+
# 그렇지 않다면, p1을 그대로 두고 max_len 업데이트
56+
else:
57+
max_len = max(max_len, p2 - p1 + 1)
58+
59+
# right를 seen에 기록
60+
seen[right] = p2
61+
62+
return max_len

number-of-islands/seungriyou.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# https://leetcode.com/problems/number-of-islands/
2+
3+
from typing import List
4+
5+
class Solution:
6+
def numIslands_bfs(self, grid: List[List[str]]) -> int:
7+
"""
8+
[Complexity]
9+
- TC: O(m * n)
10+
- SC: O(m * n)
11+
12+
[Approach]
13+
BFS를 사용한다.
14+
"""
15+
from collections import deque
16+
17+
dr, dc = [-1, 1, 0, 0], [0, 0, -1, 1]
18+
m, n = len(grid), len(grid[0])
19+
res = 0
20+
21+
def bfs(r, c):
22+
q = deque([(r, c)])
23+
grid[r][c] = "0"
24+
25+
while q:
26+
r, c = q.popleft()
27+
28+
for i in range(4):
29+
nr, nc = r + dr[i], c + dc[i]
30+
# nr, nc가 (1) 범위 내이면서 (2) 아직 방문하지 않은 곳이라면
31+
if 0 <= nr < m and 0 <= nc < n and grid[nr][nc] == "1":
32+
q.append((nr, nc))
33+
grid[nr][nc] = "0"
34+
35+
for r in range(m):
36+
for c in range(n):
37+
if grid[r][c] == "1":
38+
bfs(r, c)
39+
res += 1
40+
41+
return res
42+
43+
def numIslands(self, grid: List[List[str]]) -> int:
44+
"""
45+
[Complexity]
46+
- TC: O(m * n)
47+
- SC: O(m * n) (call stack)
48+
49+
[Approach]
50+
DFS를 사용한다.
51+
"""
52+
dr, dc = [-1, 1, 0, 0], [0, 0, -1, 1]
53+
m, n = len(grid), len(grid[0])
54+
res = 0
55+
56+
def dfs(r, c):
57+
# base condition
58+
if not (0 <= r < m and 0 <= c < n) or grid[r][c] == "0":
59+
return
60+
61+
# visited 처리
62+
grid[r][c] = "0"
63+
64+
# recur
65+
dfs(r - 1, c)
66+
dfs(r + 1, c)
67+
dfs(r, c - 1)
68+
dfs(r, c + 1)
69+
70+
for r in range(m):
71+
for c in range(n):
72+
if grid[r][c] == "1":
73+
dfs(r, c)
74+
res += 1
75+
76+
return res

reverse-linked-list/seungriyou.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# https://leetcode.com/problems/reverse-linked-list/
2+
3+
from typing import Optional
4+
5+
# Definition for singly-linked list.
6+
class ListNode:
7+
def __init__(self, val=0, next=None):
8+
self.val = val
9+
self.next = next
10+
11+
class Solution:
12+
def reverseList_iter(self, head: Optional[ListNode]) -> Optional[ListNode]:
13+
"""
14+
[Complexity]
15+
- TC: O(n)
16+
- SC: O(1)
17+
18+
[Approach]
19+
linked-list를 reverse 할 때의 핵심은
20+
(1) p1 -> p2를 p1 <- p2로 바꾸고
21+
(2) p1, p2를 모두 한 칸 앞으로 전진
22+
하는 것이다.
23+
이때, (1)에서 p2.next = p1로 바꾼다면, p2를 한 칸 앞으로 전진할 수 없기 때문에 먼저 p2.next(= p3)를 기록해둬야 한다.
24+
이를 iterative 하게 구현하면 while문을 이용할 수 있다.
25+
"""
26+
p1, p2 = None, head
27+
28+
while p2:
29+
# 1. p3 얻어놓기
30+
p3 = p2.next
31+
32+
# 2. p1 <- p2 전환
33+
p2.next = p1
34+
35+
# 3. p1 -> p2, p2 -> p3 이동
36+
p1, p2 = p2, p3
37+
38+
return p1
39+
40+
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
41+
"""
42+
[Complexity]
43+
- TC: O(n)
44+
- SC: O(n) (call stack)
45+
46+
[Approach]
47+
linked-list를 reverse 할 때의 핵심은
48+
(1) p1 -> p2를 p1 <- p2로 바꾸고
49+
(2) p1, p2를 모두 한 칸 앞으로 전진
50+
하는 것이다.
51+
이때, (1)에서 p2.next = p1로 바꾼다면, p2를 한 칸 앞으로 전진할 수 없기 때문에 먼저 p2.next(= p3)를 기록해둬야 한다.
52+
이를 recursive 하게 구현하려면 다음과 같이 base condition으로 종료조건을 추가해주면 된다.
53+
"""
54+
55+
def reverse_ll(p1, p2):
56+
# base condition
57+
if not p2:
58+
return p1
59+
60+
# recur
61+
# 1. p3 얻어놓기
62+
p3 = p2.next
63+
64+
# 2. p1 <- p2 전환
65+
p2.next = p1
66+
67+
# 3. p1 -> p2, p2 -> p3 이동
68+
return reverse_ll(p2, p3)
69+
70+
return reverse_ll(None, head)

set-matrix-zeroes/seungriyou.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# https://leetcode.com/problems/set-matrix-zeroes/
2+
3+
from typing import List
4+
5+
class Solution:
6+
def setZeroes_slow(self, matrix: List[List[int]]) -> None:
7+
"""
8+
Do not return anything, modify matrix in-place instead.
9+
10+
[Complexity]
11+
- TC: O(m * n * (m + n)) -> too slow...
12+
- SC: O(1)
13+
14+
[Approach]
15+
다음의 두 단계를 inplace로 수행한다.
16+
1. 모든 cell을 순회하며, 0을 발견하면 해당 row와 column의 값을 (0인 cell을 제외하고) 모두 #로 바꾼다.
17+
2. 다시 모든 cell을 순회하며, #을 모두 0으로 바꾼다.
18+
"""
19+
20+
m, n = len(matrix), len(matrix[0])
21+
22+
def set_row_col(r, c):
23+
for _r in range(m):
24+
# 0이 아닌 경우에만 "#"으로 바꿔야 함 **
25+
if matrix[_r][c] != 0:
26+
matrix[_r][c] = "#"
27+
28+
for _c in range(n):
29+
if matrix[r][_c] != 0:
30+
matrix[r][_c] = "#"
31+
32+
# 1. 0을 발견하면 해당 row & column을 "#"으로 바꾸기
33+
for r in range(m):
34+
for c in range(n):
35+
if matrix[r][c] == 0:
36+
# row & column을 "#"으로 바꾸기
37+
set_row_col(r, c)
38+
39+
# 2. "#"을 모두 0으로 바꾸기
40+
for r in range(m):
41+
for c in range(n):
42+
if matrix[r][c] == "#":
43+
matrix[r][c] = 0
44+
45+
def setZeroes(self, matrix: List[List[int]]) -> None:
46+
"""
47+
Do not return anything, modify matrix in-place instead.
48+
49+
[Complexity]
50+
- TC: O(m * n)
51+
- SC: O(1)
52+
53+
[Approach]
54+
matrix의 첫 번째 row와 column을 flag 기록 용도로 사용한다면, space를 O(1)로 유지하면서 time도 최적화할 수 있다.
55+
- 첫 번째 row: 각 column에 0이 있었다면, 해당 column의 칸에 0으로 기록
56+
- 첫 번째 column: 각 row에 0이 있었다면, 해당 row의 칸에 0으로 기록
57+
이렇게 첫 번째 row와 column을 flag 기록용으로 쓰기 전에, 해당 row와 column에 0이 있었는지 여부를 미리 확인해야 함에 유의한다.
58+
전체 흐름은 다음과 같다:
59+
1. 첫 번째 row와 column에 0이 존재하는지 여부 확인
60+
2. 두 번째 row와 column 부터 0이 존재하는 칸에 대해 첫 번째 row와 column에 flag 기록
61+
3. 첫 번째 row와 column의 flag 값을 기반으로, 0 채우기
62+
4. 1번에서 구해둔 값으로도 0 채우기
63+
"""
64+
65+
m, n = len(matrix), len(matrix[0])
66+
67+
# 1. 첫 번째 row와 column에 0이 존재하는지 여부 확인
68+
has_zero_in_first_row = any(matrix[0][_c] == 0 for _c in range(n))
69+
has_zero_in_first_col = any(matrix[_r][0] == 0 for _r in range(m))
70+
71+
# 2. 두 번째 row와 column 부터 0이 존재하는 칸에 대해 첫 번째 row와 column에 flag 기록
72+
for r in range(1, m):
73+
for c in range(1, n):
74+
if matrix[r][c] == 0:
75+
matrix[0][c] = 0
76+
matrix[r][0] = 0
77+
78+
# 3. 첫 번째 row와 column의 flag 값을 기반으로, 0 채우기
79+
for r in range(1, m):
80+
for c in range(1, n):
81+
if matrix[0][c] == 0 or matrix[r][0] == 0:
82+
matrix[r][c] = 0
83+
84+
# 4. 1번에서 구해둔 값으로도 0 채우기
85+
if has_zero_in_first_row:
86+
for _c in range(n):
87+
matrix[0][_c] = 0
88+
89+
if has_zero_in_first_col:
90+
for _r in range(m):
91+
matrix[_r][0] = 0

unique-paths/seungriyou.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# https://leetcode.com/problems/unique-paths/
2+
3+
class Solution:
4+
def uniquePaths_2d(self, m: int, n: int) -> int:
5+
"""
6+
[Complexity]
7+
- TC: O(m * n)
8+
- SC: O(m * n)
9+
10+
[Approach]
11+
로봇이 down or right로만 이동할 수 있으므로, 어떤 칸에 도달할 수 있는 unique path의 개수는 up 칸과 left 칸까지의 unique path의 개수를 더한 값이 된다.
12+
따라서 2D DP로 풀 수 있으며, dp table은 다음과 같다.
13+
dp[r][c] = (up 칸까지의 unique path) + (left 칸까지의 unique path)
14+
= dp[r - 1][c] + dp[r][c - 1]
15+
이때, dp table의 첫 번째 row와 column에 있는 칸들은 모두 도달할 수 있는 unique path의 개수가 1이므로 초기화해준다.
16+
"""
17+
# 첫 번째 row & column은 1로 초기화
18+
dp = [[1 for _ in range(n)] for _ in range(m)]
19+
20+
# 두 번째 row & column 부터 순회
21+
for r in range(1, m):
22+
for c in range(1, n):
23+
dp[r][c] = dp[r - 1][c] + dp[r][c - 1]
24+
25+
return dp[m - 1][n - 1]
26+
27+
def uniquePaths(self, m: int, n: int) -> int:
28+
"""
29+
[Complexity]
30+
- TC: O(m * n)
31+
- SC: O(n) (1D DP로 space optimization)
32+
33+
[Approach]
34+
2D DP에서 dp[r][c]를 구할 때 dp[r - 1][c]과 dp[r][c - 1]만 사용하므로,
35+
rolling array 기법을 이용해 1D DP로 space optimization이 가능하다.
36+
따라서 길이가 n인 1D dp list를 이용해
37+
- c(= 1 ~ n - 1)를 순회하며 dp[c] += dp[c - 1]로 업데이트하는 것을 (2D DP에서의 dp[r - 1][c]가 dp[c] 값이므로!)
38+
- row 개수인 m 번 반복 (단, 첫 번째 row를 1로 초기화 한 효과를 얻기 위해 dp를 1로 초기화 하고 m - 1번 반복)
39+
하면 된다.
40+
"""
41+
42+
# 첫 번째 row는 1로 초기화 한 효과
43+
dp = [1] * n
44+
45+
for _ in range(1, m): # m - 1 번 반복
46+
for c in range(1, n): # 첫 번째 col은 1로 초기화 한 효과
47+
dp[c] += dp[c - 1]
48+
49+
return dp[n - 1]

0 commit comments

Comments
 (0)