From d26750fac94b2d99924efd382fa9f1825800a5de Mon Sep 17 00:00:00 2001 From: seungriyou Date: Sun, 8 Jun 2025 21:44:20 +0900 Subject: [PATCH 1/5] solve(w11): 268. Missing Number --- missing-number/seungriyou.py | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 missing-number/seungriyou.py diff --git a/missing-number/seungriyou.py b/missing-number/seungriyou.py new file mode 100644 index 000000000..28032c503 --- /dev/null +++ b/missing-number/seungriyou.py @@ -0,0 +1,42 @@ +# https://leetcode.com/problems/missing-number/ + +from typing import List + +class Solution: + def missingNumber_math(self, nums: List[int]) -> int: + """ + [Complexity] + - TC: O(n) (sum(nums)) + - SC: O(1) + + [Approach] + 수학적으로 생각해보면, missing number는 (0 ~ n까지 값의 합) - (nums의 합)이 된다. + """ + # (sum of [0, n]) - (sum(nums)) + n = len(nums) + return n * (n + 1) // 2 - sum(nums) + + def missingNumber(self, nums: List[int]) -> int: + """ + [Complexity] + - TC: O(n) + - SC: O(1) + + [Approach] + bit manipulation 관점으로 접근해보면, missing number를 제외한 나머지 값은 다음 두 가지 경우에서 모두 발견된다. + (1) 0 ~ n까지의 값 + (2) nums의 원소 + missing number는 이 두 가지 케이스 중 (1)에서만 한 번 발견되므로, 이를 XOR 연산으로 검출해 낼 수 있다. + (짝수 번 등장한 값은 사라짐) + """ + + res = 0 + + # (1) 0 ~ n까지의 값 XOR + for i in range(len(nums) + 1): + res ^= i + # (2) nums의 원소 XOR + for n in nums: + res ^= n + + return res From 954a865e0f1c43092de79e9d072aa4b519b6ed57 Mon Sep 17 00:00:00 2001 From: seungriyou Date: Sun, 8 Jun 2025 22:19:18 +0900 Subject: [PATCH 2/5] solve(w11): 56. Merge Intervals --- merge-intervals/seungriyou.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 merge-intervals/seungriyou.py diff --git a/merge-intervals/seungriyou.py b/merge-intervals/seungriyou.py new file mode 100644 index 000000000..e2f845642 --- /dev/null +++ b/merge-intervals/seungriyou.py @@ -0,0 +1,30 @@ +# https://leetcode.com/problems/merge-intervals/ + +from typing import List + +class Solution: + def merge(self, intervals: List[List[int]]) -> List[List[int]]: + """ + [Complexity] + - TC: O(nlogn) (sorting) + - SC: O(1) (res 제외) + + [Approach] + intervals를 오름차순 정렬하면, 이를 순회하면서 끝 값만 비교하며 interval이 서로 겹치는 경우와 겹치지 않는 경우를 판단할 수 있다. + 이전 interval과 겹치는 경우라면, 이전 끝 값과 현재 끝 값 중 큰 값으로 업데이트하면 된다. + """ + res = [] + + # interval의 시작 값이 작은 순으로 정렬 + # -> intervals를 순회하며 끝 값만 비교하며 non-overlapping/overlapping interval 판단 가능 + intervals.sort() + + for s, e in intervals: + # (1) non-overlapping: res가 비었거나, 이전 e < 현재 s인 경우 -> 그대로 추가 + if not res or res[-1][1] < s: + res.append([s, e]) + # (2) overlapping: 이전 e >= 현재 s인 경우 -> 이전 e와 현재 e 중 큰 값으로 업데이트 + else: + res[-1][1] = max(res[-1][1], e) + + return res From 6867e8329a0bc0757da5f53f4c0e4526bb4e13d8 Mon Sep 17 00:00:00 2001 From: seungriyou Date: Wed, 11 Jun 2025 22:19:24 +0900 Subject: [PATCH 3/5] solve(w11): 261. Graph Valid Tree --- graph-valid-tree/seungriyou.py | 130 +++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 graph-valid-tree/seungriyou.py diff --git a/graph-valid-tree/seungriyou.py b/graph-valid-tree/seungriyou.py new file mode 100644 index 000000000..6480d74e5 --- /dev/null +++ b/graph-valid-tree/seungriyou.py @@ -0,0 +1,130 @@ +# https://leetcode.com/problems/graph-valid-tree/ + +from typing import List + +class Solution: + def validTree_bfs(self, n: int, edges: List[List[int]]) -> bool: + """ + [Complexity] (첫 번째 조건을 통과하면 e = n - 1일 것이므로, n으로 변환도 가능) + - TC: O(v + e) (== O(n + len(edges))) + - SC: O(v + e) (graph) + + [Approach] + valid tree란 다음의 두 조건을 만족하는 undirected graph이다. + 1) acyclic -> edges의 개수가 n - 1개인지 확인 (-> early stop 가능) + 2) connected -> 모든 node가 모두 방문되었는지, 즉 len(visited) == n인지 확인 + + 이러한 조건을 BFS로 확인할 수 있다. + """ + from collections import deque + + # edge의 개수가 n - 1이 아니라면 빠르게 반환 (-> acyclic & connected 여부 확인) + if len(edges) != n - 1: + return False + + # undirected graph 구성 + graph = [[] for _ in range(n)] + for a, b in edges: + graph[a].append(b) + graph[b].append(a) + + # 0번 노드부터 시작 + visited = {0} + q = deque([0]) + + # BFS + while q: + pos = q.popleft() + for npos in graph[pos]: + if npos not in visited: + visited.add(npos) + q.append(npos) + + # visited에 모든 노드가 들어가있다면 true (-> connected 여부 확인) + return len(visited) == n + + def validTree_dfs(self, n: int, edges: List[List[int]]) -> bool: + """ + [Complexity] + - TC: O(v + e) (== O(n + len(edges))) + - SC: O(v + e) (graph) (call stack의 경우 O(v)) + + [Approach] + valid tree의 조건을 DFS로 확인할 수 있다. + """ + # edge의 개수가 n - 1이 아니라면 빠르게 반환 (-> acyclic & connected 여부 확인) + if len(edges) != n - 1: + return False + + # undirected graph 구성 + graph = [[] for _ in range(n)] + for a, b in edges: + graph[a].append(b) + graph[b].append(a) + + visited = set() + + def dfs(pos): + # base condition + if pos in visited: + return + + # visit 처리 + visited.add(pos) + + # recur + for npos in graph[pos]: + dfs(npos) + + dfs(0) + + # visited에 모든 노드가 들어가있다면 true (-> connected 여부 확인) + return len(visited) == n + + def validTree(self, n: int, edges: List[List[int]]) -> bool: + """ + [Complexity] + - TC: O(e * α(v)) + - e 만큼 반복 + - 각 union-find는 path compression으로 인해 α(v)이며, 이는 거의 상수 + => e = n - 1이므로 O(n)으로도 표현 가능 + - SC: O(n) + + [Approach] + union-find로 undirected graph의 cycle 여부를 판단할 수 있다. + 따라서 valid tree의 두 조건 중 connected 조건을 확인할 때 union-find를 사용할 수 있다. + 1) acyclic -> edges의 개수가 n - 1개인지 확인 (-> early stop 가능) + 2) connected -> union-find 시 parent가 같은 경우가 있는지 확인 + """ + # edge의 개수가 n - 1이 아니라면 빠르게 반환 (-> acyclic & connected 여부 확인) + if len(edges) != n - 1: + return False + + # union-find functions + def find_parent(x): + if parent[x] != x: + parent[x] = find_parent(parent[x]) + return parent[x] + + def union_parent(x, y): + px, py = find_parent(x), find_parent(y) + + # cyclic 하다면(= x와 y의 parent가 같다면) False 반환 + if px == py: + return False + + # union + if px < py: + parent[py] = px + else: + parent[px] = py + + return True + + # perform union-find + parent = [i for i in range(n)] + for x, y in edges: + if not union_parent(x, y): + return False + + return True From 46590421519e1c7d067485504a1baf993c915adb Mon Sep 17 00:00:00 2001 From: seungriyou Date: Wed, 11 Jun 2025 23:11:23 +0900 Subject: [PATCH 4/5] solve(w11): 143. Reorder List --- reorder-list/seungriyou.py | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 reorder-list/seungriyou.py diff --git a/reorder-list/seungriyou.py b/reorder-list/seungriyou.py new file mode 100644 index 000000000..98f531871 --- /dev/null +++ b/reorder-list/seungriyou.py @@ -0,0 +1,42 @@ +# https://leetcode.com/problems/reorder-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 reorderList(self, head: Optional[ListNode]) -> None: + """ + Do not return anything, modify head in-place instead. + """ + """ + [Complexity] + - TC: O(n) + - SC: O(1) + + [Approach] + 1. linked-list의 중간 지점을 찾은 후, left / right list를 나눈다. + 2. right list를 reverse 한다. + 3. left와 right에서 노드를 하나씩 가져와 연결한다. + """ + + # linked-list의 중간 지점을 찾은 후, left / right list 나누기 + slow = fast = head + while fast and fast.next: + slow = slow.next # -> fast가 linked-list의 끝에 도달하면, slow는 중앙에 위치 + fast = fast.next.next + + # (slow부터 시작하는) right list를 reverse (w. 다중 할당) + prev, curr = None, slow + while curr: + curr.next, prev, curr = prev, curr, curr.next + + # left와 right list에서 노드를 각각 하나씩 가져와 연결 (w. 다중 할당) + left, right = head, prev + while right.next: + left.next, left = right, left.next + right.next, right = left, right.next From 17758f57ef7b95dd8f09c43de23d4f5f6e63dc04 Mon Sep 17 00:00:00 2001 From: seungriyou Date: Fri, 13 Jun 2025 19:52:16 +0900 Subject: [PATCH 5/5] solve(w11): 124. Binary Tree Maximum Path Sum --- binary-tree-maximum-path-sum/seungriyou.py | 61 ++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 binary-tree-maximum-path-sum/seungriyou.py diff --git a/binary-tree-maximum-path-sum/seungriyou.py b/binary-tree-maximum-path-sum/seungriyou.py new file mode 100644 index 000000000..656855891 --- /dev/null +++ b/binary-tree-maximum-path-sum/seungriyou.py @@ -0,0 +1,61 @@ +# https://leetcode.com/problems/binary-tree-maximum-path-sum/ + +from typing import Optional + +# 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 maxPathSum(self, root: Optional[TreeNode]) -> int: + """ + [Complexity] + - TC: O(n) (각 node를 한 번씩 방문) + - SC: O(height) (tree의 높이만큼 call stack) + + [Approach] + path에는 같은 node가 두 번 들어갈 수 없으므로, 각 node 마다 해당 node를 지난 path의 max sum은 다음과 같이 구할 수 있다. + = (1) left child를 지나는 path의 max_sum + + (2) right child를 지나는 path의 max_sum + + (3) 현재 node의 val + 따라서 이는 재귀 문제로 풀 수 있으며, 각 단계마다 global max sum과 비교하며 값을 업데이트 하면 된다. + + 이때 node의 값은 음수가 될 수 있으므로, (1) & (2)가 음수라면 0으로 처리해야 한다. 즉, + = (1) max(get_max_sum(node.left), 0) + + (2) max(get_max_sum(node.right), 0) + + (3) 현재 node의 val + 과 같이 구하고, global max sum과 이 값을 비교하면 된다. + + 또한, 현재 보고 있는 node가 get_max_sum() 함수의 반환값은 + = 현재 node의 값 + (1) & (2) 중 큰 값 + 이어야 한다. 왜냐하면 현재 node를 두 번 이상 방문할 수 없기 때문이다. + """ + + res = -1001 + + def get_max_sum(node): + """주어진 node가 root인 tree에서, root를 지나는 path의 max sum을 구하는 함수""" + nonlocal res + + # base condition + if not node: + return 0 + + # compute left & right child's max path sum + left_max_sum = max(get_max_sum(node.left), 0) + right_max_sum = max(get_max_sum(node.right), 0) + + # compute max path sum at this step + max_sum = left_max_sum + right_max_sum + node.val + + # update res + res = max(res, max_sum) + + return node.val + max(left_max_sum, right_max_sum) + + get_max_sum(root) + + return res