From 77bebde62f120ef8227b16130566f501ebb8996a Mon Sep 17 00:00:00 2001
From: obzva <dngyng1000@gmail.com>
Date: Mon, 14 Oct 2024 08:23:17 +0900
Subject: [PATCH 1/6] Solution: Invert Binary Tree

---
 invert-binary-tree/flynn.go | 77 +++++++++++++++++++++++++++++++++++++
 1 file changed, 77 insertions(+)
 create mode 100644 invert-binary-tree/flynn.go

diff --git a/invert-binary-tree/flynn.go b/invert-binary-tree/flynn.go
new file mode 100644
index 000000000..cd34256b3
--- /dev/null
+++ b/invert-binary-tree/flynn.go
@@ -0,0 +1,77 @@
+/*
+풀이 1
+- 함수의 재귀호출을 이용해서 풀이할 수 있습니다
+
+Big O
+- N: 노드의 개수
+- H: 트리의 높이
+- Time complexity: O(N)
+- Space complexity: O(H) (logN <= H <= N)
+  - 재귀 호출 스택의 최대 깊이는 트리의 높이에 비례하여 증가합니다
+*/
+
+/**
+ * Definition for a binary tree node.
+ * type TreeNode struct {
+ *     Val int
+ *     Left *TreeNode
+ *     Right *TreeNode
+ * }
+ */
+ func invertTree(root *TreeNode) *TreeNode {
+    if root == nil {
+        return root
+    }
+
+    tmp := invertTree(root.Right)
+    root.Right = invertTree(root.Left)
+    root.Left = tmp
+
+    return root
+}
+
+/*
+풀이 2
+- 큐와 반복문을 이용하여 풀이할 수 있습니다
+
+Big O
+- N: 노드의 개수
+- Time complexity: O(N)
+- Space complexity: O(N)
+  - 큐의 최대 크기는 N / 2 를 넘지 않습니다
+    큐의 최대 크기는 트리의 모든 층 중에서 가장 폭이 큰 층의 노드 수와 같습니다
+	높이가 H인 트리의 최대 폭은 1. balanced tree일 때 2. 맨 아랫 층의 폭이고 이 때의 폭 W는 2^(H-1) 입니다
+	높이가 H인 balanced tree의 노드 개수는 2^H - 1 = N 이므로 아래 관계가 성립합니다
+	N/2 = (2^H - 1) / 2 = 2^(H-1) - 1/2 >= 2^(H-1) = W
+	따라서 공간 복잡도는 O(N/2) = O(N) 입니다
+*/
+
+/**
+ * Definition for a binary tree node.
+ * type TreeNode struct {
+ *     Val int
+ *     Left *TreeNode
+ *     Right *TreeNode
+ * }
+ */
+ func invertTree(root *TreeNode) *TreeNode {
+    queue := make([]*TreeNode, 0)
+    queue = append(queue, root)
+
+    for len(queue) > 0 {
+        node := queue[0]
+        queue = queue[1:]
+
+        if node == nil {
+            continue
+        }
+
+        tmp := node.Left
+        node.Left = node.Right
+        node.Right = tmp
+
+        queue = append(queue, node.Left, node.Right)
+    }
+
+    return root
+}

From 79f42ebf7582dde3f23251d6cab1c8a56e5480fb Mon Sep 17 00:00:00 2001
From: obzva <dngyng1000@gmail.com>
Date: Wed, 16 Oct 2024 02:08:16 +0900
Subject: [PATCH 2/6] Solution: Search in Rotated Sorted Array

---
 search-in-rotated-sorted-array/flynn.go | 52 +++++++++++++++++++++++++
 1 file changed, 52 insertions(+)
 create mode 100644 search-in-rotated-sorted-array/flynn.go

diff --git a/search-in-rotated-sorted-array/flynn.go b/search-in-rotated-sorted-array/flynn.go
new file mode 100644
index 000000000..4ea96dcb8
--- /dev/null
+++ b/search-in-rotated-sorted-array/flynn.go
@@ -0,0 +1,52 @@
+/*
+풀이
+- 이진탐색을 두 번 사용하여 풀이할 수 있습니다
+  첫번째, pivot index를 찾습니다
+  두번째, target을 찾습니다
+
+Big O
+- N: 주어진 배열 nums의 길이
+- Time complexity: O(logN)
+  - 각 이진탐색이 모두 O(logN)의 시간 복잡도를 가집니다
+- Space complexity: O(1)
+*/
+
+func search(nums []int, target int) int {
+	n := len(nums)
+
+	lo, hi := 0, n
+	for lo < hi {
+		mid := lo + (hi-lo)/2
+		if nums[mid] > nums[n-1] {
+			lo = mid + 1
+		} else {
+			hi = mid
+		}
+	}
+	pivot := lo
+	if pivot == n {
+		pivot = 0
+	}
+
+	lo, hi = pivot, pivot+n
+	for lo < hi {
+		mid := lo + (hi-lo)/2
+		normalizedMid := mid
+		if normalizedMid >= n {
+			normalizedMid = mid - n
+		}
+		if nums[normalizedMid] <= target {
+			lo = mid + 1
+		} else {
+			hi = mid
+		}
+	}
+
+	if lo > n {
+		lo -= n
+	}
+	if lo-1 < 0 || nums[lo-1] != target {
+		return -1
+	}
+	return lo - 1
+}

From 6541b1738b7b004a750def223904ac03e6bec2ed Mon Sep 17 00:00:00 2001
From: obzva <dngyng1000@gmail.com>
Date: Wed, 16 Oct 2024 03:18:17 +0900
Subject: [PATCH 3/6] Solution: Course Schedule

---
 course-schedule/flynn.go | 65 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 65 insertions(+)
 create mode 100644 course-schedule/flynn.go

diff --git a/course-schedule/flynn.go b/course-schedule/flynn.go
new file mode 100644
index 000000000..b48da908d
--- /dev/null
+++ b/course-schedule/flynn.go
@@ -0,0 +1,65 @@
+/*
+풀이
+- 각 course를 node로 생각하고, prerequisite 관계에 있는 node를 간선으로 이어주면 단방향 간선으로 연결된 node 집합의 graph를 떠올릴 수 있습니다
+- 이 문제는 위에서 설명한 graph에 loop가 있냐 없느냐를 판단하는 문제입니다
+- 함수의 재귀호출 및 백트래킹을 이용해서 풀이할 수 있습니다
+
+Big O
+- N: 과목 수 (node의 개수)
+- M: 배열 prerequisites의 길이 (간선의 개수)
+- Time compleixty: O(N + M)
+  - prereqMap을 초기화 -> O(M)
+  - 함수의 재귀호출을 통해 우리는 각 node를 최대 한번씩 조회합니다 -> O(N)
+- Space complexity: O(N + M)
+  - prereqMap -> O(M)
+  - checkingSet, checkedSet -> O(N)
+*/
+
+func canFinish(numCourses int, prerequisites [][]int) bool {
+	// 주어진 prerequisites 배열로 `a_i: b_0, b_1, ...` 형태의 맵을 짭니다
+	prereqMap := make(map[int][]int, numCourses)
+	for _, pair := range prerequisites {
+		prereqMap[pair[0]] = append(prereqMap[pair[0]], pair[1])
+	}
+
+	// checkingSet으로 현재 탐색하고 있는 구간에 loop가 생겼는지 여부를 판단하고, checkedSet으로 이미 탐색한 node인지를 판단합니다
+	checkingSet, checkedSet := make(map[int]bool, 0), make(map[int]bool, 0)
+
+	// 특정 과목 c를 듣기 위한 선행 과목들을 탐색했을 때 loop가 생기는지 여부를 판단하는 함수입니다
+	// (Go 언어 특성상 L:20-21 처럼 함수를 선언합니다 (함수 내부에서 함수를 선언할 땐 익명 함수를 사용 + 해당 함수를 재귀호출하기 위해서 선언과 초기화를 분리))
+	var checkLoop func(int) bool // loop가 있다면 true
+	checkLoop = func(c int) bool {
+		// 과목 c가 현재 탐색하고 있는 구간에 존재한다면 loop가 있다고 판단 내릴 수 있습니다
+		_, checkingOk := checkingSet[c]
+		if checkingOk {
+			return true
+		}
+		// 과목 c가 이미 탐색이 완료된 과목이라면 과목 c를 지나는 하위 구간에는 loop가 없다고 판단할 수 있습니다
+		_, checkedOk := checkedSet[c]
+		if checkedOk {
+			return false
+		}
+		// 과목 c를 checkingSet에 추가합니다
+		// 만약 하위 구간에서 과목 c를 다시 만난다면 loop가 있다고 판단할 수 있습니다
+		checkingSet[c] = true
+		// 각 선행과목 별로 하위구간을 만들어 탐색을 진행합니다
+		// 하위구간 중 하나라도 loop가 발생하면 현재 구간에는 loop가 있다고 판단할 수 있습니다
+		for _, prereq := range prereqMap[c] {
+			if checkLoop(prereq) {
+				return true
+			}
+		}
+		// 만약 loop가 발견되지 않았다면 checkedSet에 과목 c를 추가함으로써 과목 c를 지나는 구간이 안전하다고 표시합니다
+		checkedSet[c] = true
+		// checkingSet에서 과목 c를 지워줍니다
+		delete(checkingSet, c)
+		return false
+	}
+
+	for i := 0; i < numCourses; i++ {
+		if checkLoop(i) {
+			return false
+		}
+	}
+	return true
+}

From 2a6021e8a1a535d8b85ce514a43e6959e494a0b4 Mon Sep 17 00:00:00 2001
From: obzva <dngyng1000@gmail.com>
Date: Wed, 16 Oct 2024 03:19:54 +0900
Subject: [PATCH 4/6] go format

---
 invert-binary-tree/flynn.go | 44 ++++++++++++++++++-------------------
 1 file changed, 22 insertions(+), 22 deletions(-)

diff --git a/invert-binary-tree/flynn.go b/invert-binary-tree/flynn.go
index cd34256b3..4053e1a3c 100644
--- a/invert-binary-tree/flynn.go
+++ b/invert-binary-tree/flynn.go
@@ -19,15 +19,15 @@ Big O
  * }
  */
  func invertTree(root *TreeNode) *TreeNode {
-    if root == nil {
-        return root
-    }
+	if root == nil {
+		return root
+	}
 
-    tmp := invertTree(root.Right)
-    root.Right = invertTree(root.Left)
-    root.Left = tmp
+	tmp := invertTree(root.Right)
+	root.Right = invertTree(root.Left)
+	root.Left = tmp
 
-    return root
+	return root
 }
 
 /*
@@ -54,24 +54,24 @@ Big O
  *     Right *TreeNode
  * }
  */
- func invertTree(root *TreeNode) *TreeNode {
-    queue := make([]*TreeNode, 0)
-    queue = append(queue, root)
+func invertTree(root *TreeNode) *TreeNode {
+	queue := make([]*TreeNode, 0)
+	queue = append(queue, root)
 
-    for len(queue) > 0 {
-        node := queue[0]
-        queue = queue[1:]
+	for len(queue) > 0 {
+		node := queue[0]
+		queue = queue[1:]
 
-        if node == nil {
-            continue
-        }
+		if node == nil {
+			continue
+		}
 
-        tmp := node.Left
-        node.Left = node.Right
-        node.Right = tmp
+		tmp := node.Left
+		node.Left = node.Right
+		node.Right = tmp
 
-        queue = append(queue, node.Left, node.Right)
-    }
+		queue = append(queue, node.Left, node.Right)
+	}
 
-    return root
+	return root
 }

From 92fb01ed90e8e4a80e1086ef6517eab57a05ebe2 Mon Sep 17 00:00:00 2001
From: obzva <dngyng1000@gmail.com>
Date: Wed, 16 Oct 2024 04:20:57 +0900
Subject: [PATCH 5/6] Solution: Jump Game

---
 jump-game/flynn.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 65 insertions(+)
 create mode 100644 jump-game/flynn.go

diff --git a/jump-game/flynn.go b/jump-game/flynn.go
new file mode 100644
index 000000000..678d930d5
--- /dev/null
+++ b/jump-game/flynn.go
@@ -0,0 +1,65 @@
+/*
+풀이 1
+- memo 배열을 이용하여 n-1번째 인덱스부터 0번째 인덱스 방향으로 탐색하여 풀이할 수 있습니다
+  memo[i] = i번째 인덱스에서 출발했을 때 마지막 인덱스에 도달할 수 있는지 여부
+
+Big O
+- N: 주어진 배열 nums의 길이
+- Time complexity: O(N)
+- Space complexity: O(N)
+  - 풀이 2를 이용하면 O(1)으로 최적화할 수 있습니다
+*/
+
+func canJump(nums []int) bool {
+	n := len(nums)
+
+	if nums[0] == 0 && n > 1 {
+		return false
+	}
+
+	memo := make([]bool, n)
+	memo[n-1] = true
+
+	for i := n - 2; i >= 0; i-- {
+		for j := 1; j <= nums[i]; j++ {
+			if i+j >= n {
+				break
+			}
+			if memo[i+j] {
+				memo[i] = true
+				break
+			}
+		}
+	}
+
+	return memo[0]
+}
+
+/*
+풀이
+- 풀이 1을 잘 관찰하면 memo배열의 모든 값을 가지고 있을 필요가 없다는 걸 알 수 있습니다
+  memo 배열 대신에, 문제의 조건대로 마지막 인덱스까지 갈 수 있는 가장 좌측의 인덱스만 기록합니다 (leftmost)
+
+Big O
+- N: 주어진 배열 nums의 길이
+- Time complexity: O(N)
+- Space complexity: O(1)
+*/
+
+func canJump(nums []int) bool {
+	n := len(nums)
+
+	if nums[0] == 0 && n > 1 {
+		return false
+	}
+
+	leftmost := n - 1
+
+	for i := n - 2; i >= 0; i-- {
+		if i+nums[i] >= leftmost {
+			leftmost = i
+		}
+	}
+
+	return leftmost == 0
+}

From 8c9dd170b8a9dd01118b4a74fdb178910af804d4 Mon Sep 17 00:00:00 2001
From: obzva <dngyng1000@gmail.com>
Date: Wed, 16 Oct 2024 05:22:30 +0900
Subject: [PATCH 6/6] Solution: Merge K Sorted Lists

---
 merge-k-sorted-lists/flynn.go | 127 ++++++++++++++++++++++++++++++++++
 1 file changed, 127 insertions(+)
 create mode 100644 merge-k-sorted-lists/flynn.go

diff --git a/merge-k-sorted-lists/flynn.go b/merge-k-sorted-lists/flynn.go
new file mode 100644
index 000000000..53682bd1f
--- /dev/null
+++ b/merge-k-sorted-lists/flynn.go
@@ -0,0 +1,127 @@
+/*
+풀이 1
+- lists를 순회하면서 순서대로 링크드리스트 두 개를 짝 지어 병합하는 방식으로 풀이할 수 있습니다
+
+Big O
+- K: 배열 lists의 길이
+- N: 모든 링크드리스트의 노드 개수의 합
+- n_i: i번 인덱스 링크드리스트의 노드의 개수
+- Time complexity: O(KN)
+  - K-1 번의 병합을 진행합니다
+  - i번째 병합 때, 병합하는 두 링크드리스트는 각각 𝚺(n_(i-1)), n_i입니다
+    이 때 𝚺(n_(i-1))의 상한을 고려한다면 두 링크드리스트의 병합에 걸리는 시간복잡도는 O(N)입니다
+  - O((K-1)N) = O(KN)
+  - 풀이 2로 시간복잡도를 O((logK)N)으로 최적화할 수 있습니다
+- Space complexity: O(1)
+  - res, dummy, curr 등의 추가적인 포인터를 생성하긴 하지만 기존에 주어져 있던 ListNode의 Next만 조작하므로 K, N과 상관 없이 공간복잡도는 상수값을 가집니다
+*/
+
+/**
+ * Definition for singly-linked list.
+ * type ListNode struct {
+ *     Val int
+ *     Next *ListNode
+ * }
+ */
+ func mergeKLists(lists []*ListNode) *ListNode {
+	n := len(lists)
+
+	if n == 0 {
+		return nil
+	}
+
+	res := lists[0]
+	for i := 1; i < n; i++ {
+		res = mergeTwoLists(res, lists[i])
+	}
+	return res
+}
+
+func mergeTwoLists(first *ListNode, second *ListNode) *ListNode {
+	dummy := &ListNode{}
+	curr := dummy
+
+	for first != nil && second != nil {
+		if first.Val < second.Val {
+			curr.Next = first
+			first = first.Next
+		} else {
+			curr.Next = second
+			second = second.Next
+		}
+		curr = curr.Next
+	}
+
+	if first != nil {
+		curr.Next = first
+	}
+	if second != nil {
+		curr.Next = second
+	}
+
+	return dummy.Next
+}
+
+
+/*
+풀이 2
+- Divide and Conquer 방식으로 시간복잡도를 최적화할 수 있습니다
+- 하지만 공간복잡도 측면에서는 trade-off가 있습니다
+
+Big O
+- K: 배열 lists의 길이
+- N: 모든 링크드리스트의 노드 개수의 합
+- Time complexity: O((logK)N)
+  - lists를 반으로 쪼개 가면서 재귀호출을 진행하므로 재귀호출은 logK 레벨에 걸쳐 이루어집니다 -> O(logK)
+  - 각 계층마다 우리는 모든 노드를 최대 한 번씩 조회합니다 -> O(N)
+- Space complexity: O(logK)
+  - 풀이 1과 비슷하지만 재귀호출 스택을 고려해야 합니다
+*/
+
+/**
+ * Definition for singly-linked list.
+ * type ListNode struct {
+ *     Val int
+ *     Next *ListNode
+ * }
+ */
+ func mergeKLists(lists []*ListNode) *ListNode {
+	n := len(lists)
+
+	if n == 0 {
+		return nil
+	}
+	if n == 1 {
+		return lists[0]
+	}
+
+	left := mergeKLists(lists[:n/2])
+	right := mergeKLists(lists[n/2:])
+
+	return mergeTwoLists(left, right)
+}
+
+func mergeTwoLists(first *ListNode, second *ListNode) *ListNode {
+	dummy := &ListNode{}
+	curr := dummy
+
+	for first != nil && second != nil {
+		if first.Val < second.Val {
+			curr.Next = first
+			first = first.Next
+		} else {
+			curr.Next = second
+			second = second.Next
+		}
+		curr = curr.Next
+	}
+
+	if first != nil {
+		curr.Next = first
+	}
+	if second != nil {
+		curr.Next = second
+	}
+
+	return dummy.Next
+}