Skip to content

Commit

Permalink
Merge pull request #568 from haklee/main
Browse files Browse the repository at this point in the history
[haklee] week 12
  • Loading branch information
haklee authored Nov 3, 2024
2 parents 1ffbcc7 + 611e335 commit 37d9e04
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 0 deletions.
130 changes: 130 additions & 0 deletions merge-intervals/haklee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""TC: O(l * i + n), SC: O(n)
※ 쉬운 길을 돌아가는 풀이라는 것을 감안하고 읽어야 한다!!!
※ 구간의 끝 값이 10^4라고 되어있는 것을 보고 효율 안 따지고 냅다 아래의 방법으로 접근해보았다.
n은 전체 구간의 끝 값. 문제에서는 10^4으로 주어져 있다. (구체적으로는, 구간의 시작과 끝이 [0, 10^4] 구간에 존재)
i는 전체 인터벌의 개수. 문제에서는 10^4으로 주어져 있다.
l은 각 인터벌의 크기. 문제에서는 10^4으로 주어져 있다.
아이디어:
- n칸 짜리 일직선으로 되어있는 벽이 있다고 하자. 이 벽에는 아무런 칠이 되어있지 않다.
- [0, 0, ..., 0] 리스트라고 생각하자.
- 모든 인터벌을 순회하면서 인터벌의 시작, 끝 값을 활용하여 이 벽의 일정 구간에 페인트를 칠한다고 하자.
- 특정 구간의 값을 1로 바꾼다.
- [0, ..., 0, 1, ..., 1, 0, ..., 0]
^ ^
s e - 1
- 페인트 칠이 끝났으면 벽의 시작부터 끝까지 훑으면서 칠해진 구간을 찾아내어 결과로 리턴한다.
SC:
- 벽을 길이 n짜리 리스트로 관리. O(n).
- 페인트 칠이 완료된 벽에서 구간을 찾을때 구간의 시작, 끝 인덱스를 관리하는 값에서 O(1).
- 종합하면 O(n).
TC:
- 각 인터벌을 순회할 때마다 벽 리스트에서 최대 l개의 아이템에 접근해서 값을 1로 바꾼다. O(l).
- 위의 작업을 인터벌 개수 만큼 진행. 여기까지 O(l * i).
- 페인트 칠이 끝난 벽을 순회하며 인터벌 추출. O(n).
- 종합하면 O(l * i + n).
"""


class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
max_v = int(1e4 + 2)
flags = [False] * max_v

for i in intervals:
for v in range(i[0], i[1] + 1):
flags[v] = True

res = []

make_interval = False
int_s, int_e = -1, -1

for i in range(max_v):
if flags[i]:
# 인터벌에 포함되어야 하는 값
if not make_interval:
# 인터벌의 시작 값이다. 인터벌 시작을 i로 세팅.
make_interval = True
int_s = i
else:
# 인터벌에 포함된 값이다. 인터벌 끝을 i로 세팅.
int_e = i
else:
# 인터벌에 포함 안되는 값
if make_interval:
# 직전 값까지는 인터벌에 포함되었으므로, i에서
# 인터벌이 끝났다. 인터벌을 더해줌.
res.append([int_s, int_e])
make_interval = False

return res


"""
그런데 위의 코드를 제출하면 오답이라고 나온다. 왜냐하면, 문제 조건상 인터벌의 s와 e값이 같을 수 있고,
따라서 길이 0짜리 구간이 존재할 수 있기 때문!!!!! (길이 0짜리 인터벌이라니... 분노를 금할 수 없다.)
그래서 위에서 벽에 페인트를 칠하는 비유로는 설명이 불가능한 이상한 인터벌도 결과에 포함시켜서 리턴해야 한다.
이를 위해서 별도의 처리를 한 것이 아래의 코드다. 추가된 코드를 보기 편하게 하기 위해 한글 변수명을 활용했다.
"""


class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
max_v = int(1e4 + 2)
flags = [False] * max_v
억까 = []

for i in intervals:
for v in range(i[0], i[1]):
flags[v] = True
if i[0] == i[1]:
억까.append(i[0])

억까 = list(set(억까))
억까.sort()

res = []

make_interval = False
int_s, int_e = -2, -2
억까_ind = 0

for i in range(max_v):
if flags[i]:
# 인터벌에 포함되어야 하는 값
if not make_interval:
# 길이가 있는 인터벌 앞에 오는 길이 0짜리 인터벌들을 추가해주자.
while 억까_ind < len(억까) and (v := 억까[억까_ind]) <= i:
if int_e + 1 < v:
# 직전에 결과에 추가한 인터벌의 끝 값보다는 커야 한다.
if v < i:
res.append([v, v])
억까_ind += 1

# 인터벌의 시작 값이다. 인터벌의 시작과 끝을 i로 세팅.
make_interval = True
int_s = int_e = i

else:
# 인터벌에 포함된 값이다. 인터벌 끝을 i로 세팅.
int_e = i
else:
# 인터벌에 포함 안되는 값
if make_interval:
# 직전 값까지는 인터벌에 포함되었으므로, i에서
# 인터벌이 끝났다. 인터벌을 더해줌.
res.append([int_s, int_e + 1])
make_interval = False

while 억까_ind < len(억까):
# 끝에 오는 길이 0짜리 인터벌들을 마저 추가해주자.
if int_e + 1 < (v := 억까[억까_ind]):
res.append([v, v])
억까_ind += 1

return res
63 changes: 63 additions & 0 deletions number-of-connected-components-in-an-undirected-graph/haklee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""TC: O(n), SC: O(1)
n은 주어진 노드의 개수, e는 주어진 엣지의 개수.
아이디어:
- union-find를 활용하여 disjoint set을 찾는다.
SC:
- parent, rank값 관리에 각각 길이 n짜리 리스트가 필요하다. O(n).
- 결과 값을 찾을때 각 인덱스마다 find 함수의 결과를 찾아서 리스트로 만들고, 이를 set으로 만들어서
길이 측정. 여기서도 O(n).
- 종합하면 O(n).
TC:
- union, find 각각 union by rank 적용시 O(α(n)) 만큼의 시간이 든다. 이때 α(n)은 inverse Ackermann function
으로, 매우 느린 속도로 늘어나므로 사실상 상수라고 봐도 무방하다.
- union을 e회, find를 n회 시행하므로 O((n + e) * α(n)).
- 모든 노드에 find를 시행해서 얻은 값을 set으로 만들때 리스트를 전부 순회하므로 O(n).
- 종합하면 O((n + e) * α(n)).
"""


class Solution:
"""
@param n: the number of vertices
@param edges: the edges of undirected graph
@return: the number of connected components
"""

def count_components(self, n: int, edges: List[List[int]]) -> int:
# write your code here

# union find
parent = list(range(n))
rank = [0] * n

def find(x: int) -> bool:
if x == parent[x]:
return x

parent[x] = find(parent[x]) # path-compression
return parent[x]

def union(a: int, b: int) -> bool:
pa = find(a)
pb = find(b)

# union by rank
if pa == pb:
return

if rank[pa] < rank[pb]:
pa, pb = pb, pa

parent[pb] = pa

if rank[pa] == rank[pb]:
rank[pa] += 1

for e in edges:
union(*e)

return len(set(find(i) for i in range(n)))
45 changes: 45 additions & 0 deletions remove-nth-node-from-end-of-list/haklee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""TC: O(n), SC: O(1)
n은 주어진 리스트의 길이.
아이디어:
- 길이를 먼저 측정한다.
- 그 다음 제거할 노드의 인덱스를 구해서 해당 인덱스의 아이템을 제거.
SC:
- 리스트의 길이 값 및 리스트 탐색에 사용하는 인덱스 값을 관리. O(1).
TC:
- 길이 값 구할때 리스트를 전체 순회. O(n).
- 특정 인덱스에 해당하는 노드 제거시 최악의 경우 끝 노드까지 탐색해야 한다. O(n).
- 종합하면 O(n).
"""


# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
# 길이 측정
l = 0
cur_head = head
while True:
if not cur_head:
break
l += 1
cur_head = cur_head.next

# 노드 하나 제거
i = 0
dummy_head = ListNode()
dummy_head.next = head
cur_head = dummy_head
while i != l - n:
i += 1
cur_head = cur_head.next

cur_head.next = None if cur_head.next is None else cur_head.next.next
return dummy_head.next
35 changes: 35 additions & 0 deletions same-tree/haklee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""TC: O(n), SC: O(h)
n은 주어진 트리 p, q의 노드 개수 중 더 작은 쪽의 값.
h는 주어진 트리 p의 높이.
아이디어:
- p를 dfs로 돌면서 q도 같은 순서로 dfs를 돌린다.
- 이때 순회하다가 하나라도 다른 값이 나오면 False. 모두 같으면 True.
SC:
- p를 기준으로 dfs를 돌고 있으므로 호출 스택의 깊이가 p의 깊이보다 깊어질 수 없다. O(h).
TC:
- 최악의 경우 모든 노드 순회 후 True 리턴. O(n).
- False를 리턴할 경우 트리 순회 중 멈춘다. 이 경우 두 트리 중 더 적은 노드 개수 보다
적은 회수 만큼 순회. 이 경우에도 O(n).
- 종합하면 O(n).
"""


# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
return (p is None and q is None) or (
p is not None
and q is not None
and p.val == q.val
and self.isSameTree(p.left, q.left)
and self.isSameTree(p.right, q.right)
)
74 changes: 74 additions & 0 deletions serialize-and-deserialize-binary-tree/haklee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""TC: O(n), SC: O(1)
n은 주어진 트리의 노드 개수.
아이디어:
- 트리 구조를 dict로 만들어버리자.
- Node = None | {v: int, l: Node, r: Node}
- 이 dict를 python에 있는 json 패키지를 써서 string으로 바꾸고, string에서 불러온다.
SC:
- dict에 들어가는 정보의 크기는 노드 개수만큼 커지며, 이걸 그대로 string으로 바꾸기 때문에
노드 개수에 비례하여 증가. O(n).
TC:
- serialize, deserialize 과정 모두 노드 개수만큼 순회. O(n).
"""

# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None

import json


class Codec:

def serialize(self, root):
"""Encodes a tree to a single string.
:type root: TreeNode
:rtype: str
"""

def write_node(node):
d = None
if node:
d = {
"v": node.val,
"l": write_node(node.left),
"r": write_node(node.right),
}
return d

return json.dumps(write_node(root))

def deserialize(self, data):
"""Decodes your encoded data to tree.
:type data: str
:rtype: TreeNode
"""

data = json.loads(data)

def read_data(d):
if d is None:
return None

node = TreeNode(d["v"])
node.left = read_data(d["l"])
node.right = read_data(d["r"])

return node

return read_data(data)


# Your Codec object will be instantiated and called as such:
# ser = Codec()
# deser = Codec()
# ans = deser.deserialize(ser.serialize(root))

0 comments on commit 37d9e04

Please sign in to comment.