Skip to content

Commit 795c47a

Browse files
authored
Merge branch 'DaleStudy:main' into main
2 parents 00cd79b + 82029fc commit 795c47a

File tree

20 files changed

+1122
-0
lines changed

20 files changed

+1122
-0
lines changed
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
class TreeNode {
2+
val: number;
3+
left: TreeNode | null;
4+
right: TreeNode | null;
5+
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
6+
this.val = val === undefined ? 0 : val;
7+
this.left = left === undefined ? null : left;
8+
this.right = right === undefined ? null : right;
9+
}
10+
}
11+
12+
/**
13+
* https://leetcode.com/problems/binary-tree-maximum-path-sum
14+
* T.C. O(n)
15+
* S.C. O(n)
16+
*/
17+
function maxPathSum(root: TreeNode | null): number {
18+
let max = -Infinity;
19+
20+
function dfs(node: TreeNode | null): number {
21+
if (!node) return 0;
22+
23+
const left = Math.max(0, dfs(node.left));
24+
const right = Math.max(0, dfs(node.right));
25+
26+
max = Math.max(max, node.val + left + right);
27+
28+
return node.val + Math.max(left, right);
29+
}
30+
31+
dfs(root);
32+
33+
return max;
34+
}
35+
36+
/**
37+
* iterative using stack
38+
* T.C. O(n)
39+
* S.C. O(n)
40+
*/
41+
function maxPathSum(root: TreeNode | null): number {
42+
let max = -Infinity;
43+
const stack: Array<TreeNode | null> = [root];
44+
const memo = new Map<TreeNode, number>();
45+
46+
while (stack.length) {
47+
const node = stack.pop();
48+
49+
if (!node) continue;
50+
51+
if (memo.has(node)) {
52+
const left = Math.max(0, node.left ? memo.get(node.left)! : 0);
53+
const right = Math.max(0, node.right ? memo.get(node.right)! : 0);
54+
55+
max = Math.max(max, node.val + left + right);
56+
57+
memo.set(node, node.val + Math.max(left, right));
58+
} else {
59+
stack.push(node, node.right, node.left);
60+
memo.set(node, 0);
61+
}
62+
}
63+
64+
return max;
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// TC: O(n)
2+
// visit all nodes to find maximum path
3+
// SC: O(h)
4+
// h means the high of binary tree
5+
class Solution {
6+
private int output = Integer.MIN_VALUE;
7+
public int maxPathSum(TreeNode root) {
8+
max(root);
9+
return output;
10+
}
11+
12+
private int max(TreeNode node) {
13+
if (node == null) return 0;
14+
15+
int leftSum = Math.max(max(node.left), 0);
16+
int rightSum = Math.max(max(node.right), 0);
17+
18+
int currentMax = node.val + leftSum + rightSum;
19+
20+
output = Math.max(output, currentMax);
21+
22+
return node.val + Math.max(leftSum, rightSum);
23+
}
24+
}

binary-tree-maximum-path-sum/flynn.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
풀이
3+
- post order traversal dfs를 활용하여 풀이할 수 있습니다
4+
Big O
5+
- N: node의 개수
6+
- H: Tree의 높이
7+
- Time compleixty: O(N)
8+
- 모든 node를 최대 한 번 탐색합니다
9+
- Space complexity: O(H)
10+
- 재귀 호출 스택의 깊이는 H에 비례하여 증가합니다
11+
*/
12+
13+
/**
14+
* Definition for a binary tree node.
15+
* type TreeNode struct {
16+
* Val int
17+
* Left *TreeNode
18+
* Right *TreeNode
19+
* }
20+
*/
21+
func maxPathSum(root *TreeNode) int {
22+
res := root.Val
23+
24+
var maxSubtreeSum func(*TreeNode) int
25+
maxSubtreeSum = func(node *TreeNode) int {
26+
// base case
27+
if node == nil {
28+
return 0
29+
}
30+
// left subtree로 내려갔을 때 구할 수 있는 maximum path sum
31+
// left subtree로 path를 내려갔을 때, left subtree가 sum에 기여하는 값이 0보다 작을 경우
32+
// left subtree로는 path를 내려가지 않는 것이 좋음
33+
// 따라서 left < 0 인 경우엔 left = 0
34+
left := maxSubtreeSum(node.Left)
35+
if left < 0 {
36+
left = 0
37+
}
38+
// right subtree도 left subtree와 동일함
39+
right := maxSubtreeSum(node.Right)
40+
if right < 0 {
41+
right = 0
42+
}
43+
// 현재 탐색하고 있는 node의 조상 node를 path에 포함하지 않고도
44+
// maxPathSum이 구해지는 경우가 있음
45+
if res < left+right+node.Val {
46+
res = left + right + node.Val
47+
}
48+
// 현재까지 계산한 subtree path sum을 부모 node에게 전달해야 함
49+
// 현재 node의 부모와 이어지는 path여야 하므로, node.Val + max(left, right)를 반환하면 됨
50+
subTreeSum := node.Val
51+
if left > right {
52+
subTreeSum += left
53+
} else {
54+
subTreeSum += right
55+
}
56+
return subTreeSum
57+
}
58+
59+
maxSubtreeSum(root)
60+
return res
61+
}
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""TC: O(n), SC: O(h)
2+
3+
4+
아이디어:
5+
- 각 노드를 부모, 혹은 자식 노드의 관점에서 분석할 수 있다.
6+
- 부모 노드의 관점에서 경로를 만들때:
7+
- 부모 노드는 양쪽 자식 노드에 연결된 경로를 잇는 다리 역할을 할 수 있다.
8+
- 이때 자식 노드는
9+
- 경로에 포함되지 않아도 된다. 이 경우 path에 0만큼 기여하는 것으로 볼 수 있다.
10+
- 자식 노드의 두 자식 노드 중 한 쪽의 경로와 부모 노드를 이어주는 역할을 한다.
11+
아래서 좀 더 자세히 설명.
12+
- 자식 노드의 관점에서 경로를 만들때:
13+
- 자식 노드는 부모 노드와 연결될 수 있어야 한다.
14+
- 그렇기 때문에 자신의 자식 노드 중 한 쪽과만 연결되어있을 수 있다. 만약 부모 노드와
15+
본인의 양쪽 자식 노드 모두와 연결되어 있으면 이 노드가 세 갈림길이 되어서 경로를 만들
16+
수 없기 때문.
17+
- 위의 분석을 통해 최대 경로를 만들고 싶다면, 다음의 함수를 root를 기준으로 재귀적으로 실행한다.
18+
- 특정 node가 부모 노드가 되었다고 했을때 본인의 값에 두 자식의 max(최대 경로, 0) 값을 더해서
19+
경로를 만들어본다. 이 값이 기존 solution보다 클 경우 solution을 업데이트.
20+
- 특정 node가 자식 노드가 될 경우 본인의 두 자식 중 더 큰 경로를 부모에 제공해야 한다.
21+
본인의 값에 max(왼쪽 경로, 오른쪽 경로)을 더해서 리턴.
22+
23+
SC:
24+
- solution값을 관리한다. O(1).
25+
- 호출 스택은 트리의 높이만큼 쌓일 수 있다. O(h).
26+
- 종합하면 O(h).
27+
28+
TC:
29+
- 각 노드에서 O(1) 시간이 소요되는 작업 수행.
30+
- 모든 노드에 접근하므로 O(n).
31+
"""
32+
33+
34+
# Definition for a binary tree node.
35+
# class TreeNode:
36+
# def __init__(self, val=0, left=None, right=None):
37+
# self.val = val
38+
# self.left = left
39+
# self.right = right
40+
class Solution:
41+
def maxPathSum(self, root: Optional[TreeNode]) -> int:
42+
sol = [-1001] # 노드의 최소값보다 1 작은 값. 현 문제 세팅에서 -inf 역할을 함.
43+
44+
def try_get_best_path(node):
45+
if node is None:
46+
# 노드가 비어있을때 경로 없음. 이때 이 노드로부터 얻을 수 있는 최대 경로 값을
47+
# 0으로 칠 수 있다.
48+
return 0
49+
50+
# 왼쪽, 오른쪽 노드로부터 얻을 수 있는 최대 경로 값.
51+
l = max(try_get_best_path(node.left), 0)
52+
r = max(try_get_best_path(node.right), 0)
53+
54+
# 현 노드를 다리 삼아서 양쪽 자식 노드의 경로를 이었을때 나올 수 있는 경로 값이
55+
# 최대 경로일 수도 있다. 이 값을 현 솔루션과 비교해서 업데이트 해준다.
56+
sol[0] = max(node.val + l + r, sol[0])
57+
58+
# 현 노드의 부모 노드가 `이 노드를 통해 얻을 수 있는 최대 경로 값`으로 사용할 값을 리턴.
59+
return node.val + max(l, r)
60+
61+
try_get_best_path(root)
62+
return sol[0]

graph-valid-tree/HC-kang.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* https://leetcode.com/problems/graph-valid-tree
3+
* T.C. O(n)
4+
* S.C. O(n)
5+
*/
6+
function validTree(n: number, edges: number[][]): boolean {
7+
if (edges.length !== n - 1) return false;
8+
9+
const parent = Array.from({ length: n }, (_, i) => i);
10+
11+
// find and compress path
12+
function find(x: number): number {
13+
if (parent[x] !== x) {
14+
parent[x] = find(parent[x]);
15+
}
16+
return parent[x];
17+
}
18+
19+
// union two sets and check if they have the same root
20+
function union(x: number, y: number): boolean {
21+
const rootX = find(x);
22+
const rootY = find(y);
23+
if (rootX === rootY) return false;
24+
parent[rootX] = rootY;
25+
return true;
26+
}
27+
28+
for (const [x, y] of edges) {
29+
if (!union(x, y)) return false;
30+
}
31+
32+
return true;
33+
}

graph-valid-tree/TonyKim9401.java

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// TC: O(n * m)
2+
// n = the length of edges, m = the length of each element list of edges
3+
// SC: O(n)
4+
// n = recursively visit everywhere
5+
public class Solution {
6+
public boolean validTree(int n, int[][] edges) {
7+
if (edges.length != n - 1) return false;
8+
9+
List<List<Integer>> graph = new ArrayList<>();
10+
for (int i = 0; i < n; i++) {
11+
graph.add(new ArrayList<>());
12+
}
13+
14+
for (int[] edge : edges) {
15+
graph.get(edge[0]).add(edge[1]);
16+
graph.get(edge[1]).add(edge[0]);
17+
}
18+
19+
boolean[] visited = new boolean[n];
20+
if (!dfs(0, -1, graph, visited)) return false;
21+
22+
for (boolean v : visited) if(!v) return false;
23+
24+
return true;
25+
}
26+
27+
private boolean dfs(int node, int parent, List<List<Integer>> graph, boolean[] visited) {
28+
visited[node] = true;
29+
30+
for (int neighbor : graph.get(node)) {
31+
if (neighbor == parent) continue;
32+
if (visited[neighbor]) return false;
33+
if (!dfs(neighbor, node, graph, visited)) return false;
34+
}
35+
return true;
36+
}
37+
}

graph-valid-tree/flynn.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
풀이
3+
- valid tree인지 판별하는 문제입니다
4+
주어진 input이 valid tree이려면,
5+
1. cycle이 없어야 합니다 (cycle이 있는 경우: [[0, 1], [1, 2], [2, 0]])
6+
2. 모든 node가 연결되어 있어야 합니다 (모든 node가 연결되지 않은 경우: [[0, 1], [2, 3]])
7+
- dfs 방식의 함수를 재귀 호출하여 풀이할 수 있습니다
8+
Big O
9+
- N: n
10+
- E: 주어진 배열 edges의 크기
11+
- Time complexity: O(N)
12+
- 모든 node를 최대 1번씩 탐색합니다
13+
- Space complexity: O(E + N)
14+
- visited의 크기는 N에 비례하여 증가합니다 -> O(N)
15+
- adj의 크기는 E에 비례하여 증가합니다 -> O(E)
16+
- dfs의 재귀호출 스택 깊이는 최악의 경우 N까지 커질 수 있습니다 -> O(N)
17+
*/
18+
19+
func validTree(n int, edges [][]int) bool {
20+
// valid tree는 n-1개의 edge를 가질 수 밖에 없습validTree
21+
// 아래 판별식을 이용하면 유효하지 않은 input에 대해 상당한 연산을 줄일 수 있습니다
22+
if len(edges) != n-1 {
23+
return false
24+
}
25+
// 주어진 2차원 배열 edges를 이용해 adjacency list를 생성합니다
26+
adj := make([][]int, n)
27+
for _, edge := range edges {
28+
adj[edge[0]] = append(adj[edge[0]], edge[1])
29+
adj[edge[1]] = append(adj[edge[1]], edge[0])
30+
}
31+
// cycle이 있는지 여부를 판단하기 위해 visited라는 map을 생성합니다 (Go에서는 map으로 set 기능을 대신함)
32+
visited := make(map[int]bool)
33+
34+
var dfs func(int, int) bool
35+
dfs = func(node int, parent int) bool {
36+
// cycle 발견시 false return
37+
if _, ok := visited[node]; ok {
38+
return false
39+
}
40+
visited[node] = true
41+
for _, next := range adj[node] {
42+
if next == parent {
43+
continue
44+
}
45+
if !dfs(next, node) {
46+
return false
47+
}
48+
}
49+
return true
50+
}
51+
// cycle 여부를 판단합니다
52+
if !dfs(0, -1) {
53+
return false
54+
}
55+
// node가 모두 연결되어 있는지 여부를 판단합니다
56+
return len(visited) == n
57+
}

0 commit comments

Comments
 (0)