From 5facfccc26fa988c51d77446cbbb199859bb2465 Mon Sep 17 00:00:00 2001 From: obzva Date: Tue, 22 Oct 2024 16:29:30 +0900 Subject: [PATCH 1/4] week 11 --- graph-valid-tree/flynn.go | 57 ++++++++++++++++++ maximum-depth-of-binary-tree/flynn.go | 83 +++++++++++++++++++++++++++ reorder-list/flynn.go | 33 +++++++++++ 3 files changed, 173 insertions(+) create mode 100644 graph-valid-tree/flynn.go create mode 100644 maximum-depth-of-binary-tree/flynn.go create mode 100644 reorder-list/flynn.go diff --git a/graph-valid-tree/flynn.go b/graph-valid-tree/flynn.go new file mode 100644 index 000000000..0cb363349 --- /dev/null +++ b/graph-valid-tree/flynn.go @@ -0,0 +1,57 @@ +/* +풀이 +- valid tree인지 판별하는 문제입니다 + 주어진 input이 valid tree이려면, + 1. cycle이 없어야 합니다 (cycle이 있는 경우: [[0, 1], [1, 2], [2, 0]]) + 2. 모든 node가 연결되어 있어야 합니다 (모든 node가 연결되지 않은 경우: [[0, 1], [2, 3]]) +- dfs 방식의 함수를 재귀 호출하여 풀이할 수 있습니다 +Big O +- N: n +- E: 주어진 배열 edges의 크기 +- Time complexity: O(N) + - 모든 node를 최대 1번씩 탐색합니다 +- Space complexity: O(E + N) + - visited의 크기는 N에 비례하여 증가합니다 -> O(N) + - adj의 크기는 E에 비례하여 증가합니다 -> O(E) + - dfs의 재귀호출 스택 깊이는 최악의 경우 N까지 커질 수 있습니다 -> O(N) +*/ + +func validTree(n int, edges [][]int) bool { + // valid tree는 n-1개의 edge를 가질 수 밖에 없습validTree + // 아래 판별식을 이용하면 유효하지 않은 input에 대해 상당한 연산을 줄일 수 있습니다 + if len(edges) != n-1 { + return false + } + // 주어진 2차원 배열 edges를 이용해 adjacency list를 생성합니다 + adj := make([][]int, n) + for _, edge := range edges { + adj[edge[0]] = append(adj[edge[0]], edge[1]) + adj[edge[1]] = append(adj[edge[1]], edge[0]) + } + // cycle이 있는지 여부를 판단하기 위해 visited라는 map을 생성합니다 (Go에서는 map으로 set 기능을 대신함) + visited := make(map[int]bool) + + var dfs func(int, int) bool + dfs = func(node int, parent int) bool { + // cycle 발견시 false return + if _, ok := visited[node]; ok { + return false + } + visited[node] = true + for _, next := range adj[node] { + if next == parent { + continue + } + if !dfs(next, node) { + return false + } + } + return true + } + // cycle 여부를 판단합니다 + if !dfs(0, -1) { + return false + } + // node가 모두 연결되어 있는지 여부를 판단합니다 + return len(visited) == n +} diff --git a/maximum-depth-of-binary-tree/flynn.go b/maximum-depth-of-binary-tree/flynn.go new file mode 100644 index 000000000..177ada065 --- /dev/null +++ b/maximum-depth-of-binary-tree/flynn.go @@ -0,0 +1,83 @@ +/* +풀이 +- DFS +Big O +- N: 노드의 개수 +- H: 트리의 높이 +- Time complexity: O(N) + - 모든 노드를 탐색합니다 +- Space complexity: O(H) + - 재귀호출 스택의 크기는 트리의 높이에 비례하여 증가합니다 +*/ + +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ + func maxDepth(root *TreeNode) int { + maxDepth := 0 + var dig func(*TreeNode, int) + dig = func(node *TreeNode, depth int) { + if node == nil { + if maxDepth < depth { + maxDepth = depth + } + return + } + dig(node.Left, depth+1) + dig(node.Right, depth+1) + } + dig(root, 0) + return maxDepth +} + +/* +풀이 +- BFS +Big O +- N: 노드의 개수 +- Time complexity: O(N) + - 모든 노드를 탐색합니다 +- Space complexity: O(N) + - 노드 N개 짜리 트리에서 한 층의 폭은 N을 넘지 않습니다 + 따라서 queue의 공간복잡도는 O(N)입니다 +*/ + +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func maxDepth(root *TreeNode) int { + level := 0 + queue := make([]*TreeNode, 0) + if root != nil { + queue = append(queue, root) + level++ + } + for len(queue) > 0 { + currQSize := len(queue) + for currQSize > 0 { + node := queue[0] + queue = queue[1:] + currQSize-- + if node.Left != nil { + queue = append(queue, node.Left) + } + if node.Right != nil { + queue = append(queue, node.Right) + } + } + if len(queue) > 0 { + level++ + } + } + return level +} diff --git a/reorder-list/flynn.go b/reorder-list/flynn.go new file mode 100644 index 000000000..30cb30fa1 --- /dev/null +++ b/reorder-list/flynn.go @@ -0,0 +1,33 @@ +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ + func reorderList(head *ListNode) { + // find a middle node + slow, fast := head, head + for fast != nil && fast.Next != nil { + slow = slow.Next + fast = fast.Next.Next + } + // reverse the second part of the list + var prev, curr *ListNode = nil, slow + for curr != nil { + tmp := curr.Next + curr.Next = prev + prev = curr + curr = tmp + } + // merge two parts of the list + curr1, curr2 := head, prev + for curr2.Next != nil { + tmp := curr1.Next + curr1.Next = curr2 + curr1 = tmp + tmp = curr2.Next + curr2.Next = curr1 + curr2 = tmp + } +} From 22682848299b5db38e1fdea1e7adb7083eeef700 Mon Sep 17 00:00:00 2001 From: obzva Date: Wed, 23 Oct 2024 17:07:14 +0900 Subject: [PATCH 2/4] Solution: insert interval --- insert-interval/flynn.go | 89 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 insert-interval/flynn.go diff --git a/insert-interval/flynn.go b/insert-interval/flynn.go new file mode 100644 index 000000000..a851e64ca --- /dev/null +++ b/insert-interval/flynn.go @@ -0,0 +1,89 @@ +/* +풀이 +- newInterval의 start와 end에 대해 각각 이진탐색을 진행하여 insert할 index를 찾아낼 수 있습니다 +- index의 앞뒤 interval과 newInterval을 비교하여 merge 여부를 판별하면 문제에서 원하는 배열을 만들 수 있습니다 +Big O +- N: 주어진 배열 intervals의 길이 +- Time complexity: O(N) + - 이진 탐색 -> O(logN) + - append(res, ...) -> O(N) + - O(N + logN) = O(N) +- Space complexity: O(N) + - 반환하는 배열 res의 공간 복잡도를 고려하면 O(N) +*/ + +func insert(intervals [][]int, newInterval []int) [][]int { + n := len(intervals) + // base case + if n == 0 { + return append(intervals, newInterval) + } + // 이진탐색 함수 + // isStart: newInterval의 start를 탐색할 땐 true, end를 탐색할 땐 false + // target보다 큰 값 중에서 가장 작은 index를 반환함 + var binarySearch func(int, bool) int + binarySearch = func(target int, isStart bool) int { + lo := 0 + hi := len(intervals) + for lo < hi { + mid := lo + (hi-lo)/2 + if isStart { + if intervals[mid][0] < target { + lo = mid + 1 + } else { + hi = mid + } + } else { + if intervals[mid][1] < target { + lo = mid + 1 + } else { + hi = mid + } + } + } + return lo + } + + start := binarySearch(newInterval[0], true) + // newInterval의 시작 지점이 intervals[start-1]의 끝 지점보다 작거나 같으면 merge해야 함 + mergeStart := start > 0 && newInterval[0] <= intervals[start-1][1] + end := binarySearch(newInterval[1], false) - 1 + // newInterval의 끝 지점이 intervals[end+1]의 시작 지점보다 크거나 같으면 merge해야 함 + mergeEnd := end+1 < n && newInterval[1] >= intervals[end+1][0] + + // -Go에서의 최적화를 위한 코드입니다- + resCapacity := n + 1 + if mergeStart { + resCapacity-- + } + if mergeEnd { + resCapacity-- + } + // ----------------------------- + res := make([][]int, 0, resCapacity) + // newInterval이 들어갈 index보다 앞 부분의 값들을 res에 append + if mergeStart { + res = append(res, intervals[:start-1]...) + } else { + res = append(res, intervals[:start]...) + } + // newInterval을 res에 append + // mergeStart, mergeEnd 여부에 따라 병합할지 그대로 넣을지 판단 + if mergeStart && mergeEnd { + res = append(res, []int{intervals[start-1][0], intervals[end+1][1]}) + } else if mergeStart { + res = append(res, []int{intervals[start-1][0], newInterval[1]}) + } else if mergeEnd { + res = append(res, []int{newInterval[0], intervals[end+1][1]}) + } else { + res = append(res, newInterval) + } + // newInterval이 들어갈 index보다 뒷 부분의 값들을 res에 append + if mergeEnd { + res = append(res, intervals[end+2:]...) + } else { + res = append(res, intervals[end+1:]...) + } + + return res +} From 74f44562f1aff2fb5ec50ac7be235701490977d3 Mon Sep 17 00:00:00 2001 From: obzva Date: Wed, 23 Oct 2024 18:00:32 +0900 Subject: [PATCH 3/4] solutionl: binary tree maximum path sum --- binary-tree-maximum-path-sum/flynn.go | 61 +++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 binary-tree-maximum-path-sum/flynn.go diff --git a/binary-tree-maximum-path-sum/flynn.go b/binary-tree-maximum-path-sum/flynn.go new file mode 100644 index 000000000..b88e887c3 --- /dev/null +++ b/binary-tree-maximum-path-sum/flynn.go @@ -0,0 +1,61 @@ +/* +풀이 +- post order traversal dfs를 활용하여 풀이할 수 있습니다 +Big O +- N: node의 개수 +- H: Tree의 높이 +- Time compleixty: O(N) + - 모든 node를 최대 한 번 탐색합니다 +- Space complexity: O(H) + - 재귀 호출 스택의 깊이는 H에 비례하여 증가합니다 +*/ + +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ + func maxPathSum(root *TreeNode) int { + res := root.Val + + var maxSubtreeSum func(*TreeNode) int + maxSubtreeSum = func(node *TreeNode) int { + // base case + if node == nil { + return 0 + } + // left subtree로 내려갔을 때 구할 수 있는 maximum path sum + // left subtree로 path를 내려갔을 때, left subtree가 sum에 기여하는 값이 0보다 작을 경우 + // left subtree로는 path를 내려가지 않는 것이 좋음 + // 따라서 left < 0 인 경우엔 left = 0 + left := maxSubtreeSum(node.Left) + if left < 0 { + left = 0 + } + // right subtree도 left subtree와 동일함 + right := maxSubtreeSum(node.Right) + if right < 0 { + right = 0 + } + // 현재 탐색하고 있는 node의 조상 node를 path에 포함하지 않고도 + // maxPathSum이 구해지는 경우가 있음 + if res < left+right+node.Val { + res = left + right + node.Val + } + // 현재까지 계산한 subtree path sum을 부모 node에게 전달해야 함 + // 현재 node의 부모와 이어지는 path여야 하므로, node.Val + max(left, right)를 반환하면 됨 + subTreeSum := node.Val + if left > right { + subTreeSum += left + } else { + subTreeSum += right + } + return subTreeSum + } + + maxSubtreeSum(root) + return res +} From 500005359879cc72162fdf988c0b5ed5cfd85ad8 Mon Sep 17 00:00:00 2001 From: obzva Date: Sat, 26 Oct 2024 15:28:28 +0900 Subject: [PATCH 4/4] fix: refactor list --- reorder-list/flynn.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/reorder-list/flynn.go b/reorder-list/flynn.go index 30cb30fa1..b08cab9d2 100644 --- a/reorder-list/flynn.go +++ b/reorder-list/flynn.go @@ -21,13 +21,9 @@ curr = tmp } // merge two parts of the list - curr1, curr2 := head, prev - for curr2.Next != nil { - tmp := curr1.Next - curr1.Next = curr2 - curr1 = tmp - tmp = curr2.Next - curr2.Next = curr1 - curr2 = tmp + for curr1, curr2 := head, prev; curr2.Next != nil; { + tmp1, tmp2 := curr1.Next, curr2.Next + curr1.Next, curr2.Next = curr2, curr1.Next + curr1, curr2 = tmp1, tmp2 } }