From bb6378c4eb17fb2c6384d6cbbab466fab77f3c4e Mon Sep 17 00:00:00 2001 From: Dusuna <94776135+dusunax@users.noreply.github.com> Date: Wed, 26 Feb 2025 23:50:29 +0900 Subject: [PATCH 1/4] add solution: same-tree --- same-tree/dusunax.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 same-tree/dusunax.py diff --git a/same-tree/dusunax.py b/same-tree/dusunax.py new file mode 100644 index 000000000..d1e4bd7ee --- /dev/null +++ b/same-tree/dusunax.py @@ -0,0 +1,37 @@ +''' +# 100. Same Tree + +## Base case +- if both nodes are None, return True +- if one of the nodes is None, return False +- if the values of the nodes are different, return False + +## Recursive case +- check if the left subtrees are the same +- check if the right subtrees are the same + +## Time and Space Complexity + +``` +TC: O(n) +SC: O(n) +``` + +#### TC is O(n): +- visiting each node once, balanced or skewed, it's O(n) + +#### SC is O(n): +- for h is tree's height. + - in best case(balanced): h is logN, so SC is O(logN) + - in worst case(skewed): h is N, so SC is O(N) +''' +class Solution: + def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool: + if not p and not q: + return True + if not p or not q: + return False + if p.val != q.val: + return False + + return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) From 94106fc467c76a0ee178d55ad0ad584cf57e7a48 Mon Sep 17 00:00:00 2001 From: Dusuna <94776135+dusunax@users.noreply.github.com> Date: Thu, 27 Feb 2025 23:54:13 +0900 Subject: [PATCH 2/4] add solution: remove-nth-node-from-end-of-list --- remove-nth-node-from-end-of-list/dusunax.py | 70 +++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 remove-nth-node-from-end-of-list/dusunax.py diff --git a/remove-nth-node-from-end-of-list/dusunax.py b/remove-nth-node-from-end-of-list/dusunax.py new file mode 100644 index 000000000..805d34d5f --- /dev/null +++ b/remove-nth-node-from-end-of-list/dusunax.py @@ -0,0 +1,70 @@ +''' +# 19. Remove Nth Node From End of List + +## 풀이 접근방식 레퍼런스 +- https://www.algodale.com/problems/remove-nth-node-from-end-of-list/ +''' +class Solution: + def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: + ''' + # 1. 노드 리스트 + - 음수 인덱스 원소 접근 + - nodes 배열을 사용하여 마지막 n번째 노드를 찾는다. + - 메모리 낭비 + TC: O(N), SC: O(N) + ''' + # nodes = [] # 배열 + # temp = ListNode(None, head) + # node = temp + + # while node: + # nodes.append(node) + # node = node.next + + # nodes[-n - 1].next = nodes[-n - 1].next.next + + # return temp.next + + ''' + # 2. 큐 + - 큐를 사용하여 마지막 n번째 노드를 찾는다. + - 최대 n+1개의 노드(필요한 범위)만 유지, 슬라이딩 윈도우 + TC: O(N), SC: O(N) + ''' + # queue = deque() #큐 + # temp = ListNode(None, head) + # node = temp + + # for _ in range(n + 1): + # queue.append(node) + # node = node.next + + # while node: + # queue.popleft() + # queue.append(node) + # node = node.next + + # queue[0].next = queue[0].next.next + # return temp.next + + ''' + # 3. 포인터 + - 연결 리스트의 특성 이용 + - 첫번째 포인터는 n번 이동하여 마지막 노드를 찾는다. + - 두번째 포인터는 첫번째 포인터가 마지막 노드를 찾을 때까지 이동한다. + - 두번째 포인터는 첫번째 포인터가 마지막 노드를 찾으면 첫번째 포인터의 이전 노드를 삭제한다. + TC: O(N), SC: O(1) + ''' + first = head + for _ in range(n): + first = first.next + + temp = ListNode(None, head) + second = temp + + while first: + first = first.next + second = second.next + + second.next = second.next.next + return temp.next From 50791a818ce5b7fd80ee349c1049a62c9d2619d4 Mon Sep 17 00:00:00 2001 From: Dusuna <94776135+dusunax@users.noreply.github.com> Date: Fri, 28 Feb 2025 15:01:18 +0900 Subject: [PATCH 3/4] add solution: number-of-connected-components-in-an-undirected-graph --- .../dusunax.py | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 number-of-connected-components-in-an-undirected-graph/dusunax.py diff --git a/number-of-connected-components-in-an-undirected-graph/dusunax.py b/number-of-connected-components-in-an-undirected-graph/dusunax.py new file mode 100644 index 000000000..33d471546 --- /dev/null +++ b/number-of-connected-components-in-an-undirected-graph/dusunax.py @@ -0,0 +1,107 @@ +''' +# 323. Number of Connected Components in an Undirected Graph + +- 무방향 그래프에서 연결된 컴포넌트(connected component)의 개수 구하기 +- 노드 개수 n: 0 ~ n-1 +- 간선 리스트 edges: 노드 쌍 [u, v] +- 연결된 컴포넌트의 개수를 반환한다. + +## 그래프 +- edges를 통해 인접 리스트(adjacency list)를 생성한다. +- 무방향 그래프이므로, u ↔ v 양쪽 방향으로 연결 + +## 풀이 알고리즘 +- DFS(스택)/BFS(큐) + - 각 컴포넌트를 탐색하며 방문 처리 + - 방문하지 않은 노드를 찾으면 새로운 컴포넌트이므로 카운트 증가 +- Union-Find + - 각 노드가 어떤 그룹에 속하는 지 parent 배열로 표현 + - find로 부모를 찾고, union으로 노드를 합친다. + - 최종적으로 독립된 그룹의 개수를 반환한다. +''' + +''' +1. DFS(스택) +''' +def countComponentsDFS(self, n: int, edges: List[List[int]]) -> int: + graph = defaultdict(list) + for u, v in edges: # 인접 리스트 생성 + graph[u].append(v) + graph[v].append(u) + + visited = set() # 방문처리할 set + count = 0 + + def dfs(start): + stack = [start] + visited.add(start) + + while stack: + curr = stack.pop() + for neighbor in graph[curr]: + if neighbor not in visited: + visited.add(neighbor) + stack.append(neighbor) + + for node in range(n): + if node not in visited: + dfs(node) + count += 1 + + return count + +''' +2. BFS(큐) +''' +def countComponentsBFS(self, n: int, edges: List[List[int]]) -> int: + graph = defaultdict(list) + for u, v in edges: # 인접 리스트 생성 + graph[u].append(v) + graph[v].append(u) + + visited = set() # 방문처리할 set + count = 0 + + def bfs(start): + queue = deque([start]) + visited.add(start) + + while queue: + curr = queue.popleft() + for neighbor in graph[curr]: + if neighbor not in visited: + visited.add(neighbor) + queue.append(neighbor) + + for node in range(n): + if node not in visited: + bfs(node) + count += 1 + + return count + +''' +3. Union-Find +- 간선을 순회하며 두 노드를 union으로 합친다.(parent 업데이트) +- find는 경로 압축을 통해 대표(root)를 찾는다.(parent에서 재귀로 찾기) +- 최종 root를 조사하고, 서로 다른 root의 개수를 반환한다. +''' +def countComponentsUnionFind(self, n: int, edges: List[List[int]]) -> int: + parent = list(range(n)) # 각 노드의 부모를 자신으로 초기화 + + def find(x): + if parent[x] != x: + parent[x] = find(parent[x]) # 경로 압축 path compression + return parent[x] + + def union(x, y): + rootX = find(x) + rootY = find(y) + if rootX != rootY: + parent[rootX] = rootY + + for u, v in edges: + union(u, v) + + # 모든 노드의 최종 부모의, 유일한 root의 개수를 반환 + return len(set(find(i) for i in range(n))) From 6079c79ed5b35827d27900e8715965ccb812a0cb Mon Sep 17 00:00:00 2001 From: Dusuna <94776135+dusunax@users.noreply.github.com> Date: Fri, 28 Feb 2025 21:01:01 +0900 Subject: [PATCH 4/4] add solution: number-of-connected-components-in-an-undirected-graph --- non-overlapping-intervals/dusunax.py | 53 ++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 non-overlapping-intervals/dusunax.py diff --git a/non-overlapping-intervals/dusunax.py b/non-overlapping-intervals/dusunax.py new file mode 100644 index 000000000..182744ee3 --- /dev/null +++ b/non-overlapping-intervals/dusunax.py @@ -0,0 +1,53 @@ +''' +# 435. Non-overlapping Intervals + +## understanding the problem +- 겹치지 않는 인터벌을 최대한 많이 남기기 위해, 지워야하는 인터벌의 최소값 반환 +- not it: 그래프에 순환이 있는지 여부를 알아본다❌ overkill +- core approach: Greedy + +## Greedy +- Whenever you detect an overlap, you should remove one. + - not physically. we'll going to return counts, so just keep counts is enough. +- Goal: keep many non-overlapping intervals as possible to minimize removal. + +### how to detect overlap? +1. sort intervals by ends +2. iterate through intervals while tracking down the prev_end(last vaild end time) + - if `current_start < prev_end`, it's overlap. + - Example: + - [2, 4] is overlap with [1, 3] + - start (2) is smaller than end (3) + +### which one should removed? +- when overlap happens, remove the interval that ends later + - it will restrict more future intervals. + - the longer an interval lasts, the more it blocks others(leaving less room for non-overlapping intervals) + +## complexity + +### TC is O(n log n) +- sorting: O(n log n) +- iterating: O(n) +- total: O(n log n) + +### SC is O(1) +- no extra space is used +''' +class Solution: + def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int: + intervals.sort(key=lambda x: x[1]) + + count = 0 # number of removals + prev_end = intervals[0][1] # last valid end time + + for start, end in intervals[1:]: # 1 ~ n-1 + if start < prev_end: # overlap detected + count += 1 + # + # prev_end is still pointing at the previous interval + # so it's keeping the interval ends earlier. (removing the longer one) + else: + prev_end = end + + return count