Skip to content

Commit a944a26

Browse files
authored
Merge pull request DaleStudy#132 from Invidam/week07-invidam
[Invidam] Week 7
2 parents 07cb561 + 5a87190 commit a944a26

File tree

5 files changed

+404
-0
lines changed

5 files changed

+404
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Intuition
2+
<!-- Describe your first thoughts on how to solve this problem. -->
3+
트리를 순회하는 방법들 중, 레벨 순서대로 순회하는 방식을 알고있어 이를 이용했다.
4+
# Approach
5+
1. BFS를 진행한다.
6+
2. 진행하기 전, 해당 레벨에 몇개의 원소가 있는지(`currLen`)을 저장한다.
7+
3. 저장된 원소만큼 순회했다면, 레벨을 다 둘러본 것이므로 부가작업(`levels = append(levels, level)`)을 한다.
8+
9+
# Complexity
10+
- Time complexity: $O(n)$
11+
- 트리의 원소를 n개라고 했을 때, 모든 원소를 순회하는 비용 `O(n)`이 소모된다.
12+
- Space complexity: $O(n)$
13+
- 트리의 원소를 n개라고 했을 때, 모든 원소들을 저장하는 배열이 `O(n)`을 소모한다.
14+
15+
# Code
16+
```go
17+
func levelOrder(root *TreeNode) [][]int {
18+
levels := make([][]int, 0)
19+
20+
if root == nil {
21+
return levels
22+
}
23+
24+
q := []*TreeNode{root}
25+
26+
for len(q) > 0 {
27+
level := make([]int, 0)
28+
currLen := len(q)
29+
for i := 0; i < currLen; i++ {
30+
front := q[0]
31+
level = append(level, front.Val)
32+
q = q[1:]
33+
34+
if front.Left != nil {
35+
q = append(q, front.Left)
36+
}
37+
if front.Right != nil {
38+
q = append(q, front.Right)
39+
}
40+
}
41+
levels = append(levels, level)
42+
}
43+
44+
return levels
45+
}
46+
47+
```
48+
49+
```go
50+
func levelOrder(root *TreeNode) [][]int {
51+
levels := make([][]int, 0)
52+
53+
if root == nil {
54+
return levels
55+
}
56+
57+
q := []*TreeNode{root}
58+
var nextQ []*TreeNode
59+
60+
for len(q) > 0 {
61+
level := make([]int, 0)
62+
for _, front := range q {
63+
level = append(level, front.Val)
64+
65+
if front.Left != nil {
66+
nextQ = append(nextQ, front.Left)
67+
}
68+
if front.Right != nil {
69+
nextQ = append(nextQ, front.Right)
70+
}
71+
}
72+
q, nextQ = nextQ, q[:0]
73+
74+
levels = append(levels, level)
75+
}
76+
77+
return levels
78+
}
79+
80+
```
81+
- 첫 번째 코드는 `q[1:]`을 이용해서 큐의 `pop()`을 구현한다. 하지만 GoLang에서는 `pop()`을 한다고해서, 참조가 해제되지 않아 메모리를 계속 잡아먹는다.
82+
- `pop()`을 하더라도 `q`가 순회하는 모든 원소들을 참조하고 있다.
83+
- 두 번째 코드는 `q, nextQ = nextQ, q[:0]`을 이용해서 `q``nextQ`의 메모리 영역을 교체할 뿐, 부가적인 메모리 공간을 필요로 하지 않아 더욱 효율적이다.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Intuition
2+
이전에 풀어봤던 문제였다! 어려운 문제였다는 생각을 벗어던지고 최대한 간단하게 풀어보려고 했다.
3+
# Approach
4+
<!-- Describe your approach to solving the problem. -->
5+
1. 두 노드(`p`, `q`) 모두 어떠한 경로를 거쳐, 해당 노드까지 도착하는지 조상들을 배열(`routes`)에 저장한다.
6+
2. 배열을 하나씩 비교해가며, 두 노드가 엇갈리는 지점(`pRoutes[idx] != qRoutes[idx]`)을 찾는다.
7+
3. 그 지점 바로 이전 지점(`[idx-1]`)이 최소 공통 조상이다.
8+
# Complexity
9+
- Time complexity
10+
- 평균: $O(log(n))$
11+
- 최악: $O(n)$
12+
- 트리의 높이만큼 순회를 하게된다. 노드가 n개 이므로, 트리의 높이는 최선 `log(n)`, 최악 `n`이 된다.
13+
14+
- Space complexity
15+
- 평균: $O(log(n))$
16+
- 최악: $O(n)$
17+
- 트리의 높이만큼 순회를 하게된다. 노드가 n개 이므로, 트리의 높이는 최선 `log(n)`, 최악 `n`이 된다.
18+
19+
# Code
20+
```go
21+
func getRoutes(head, target *TreeNode) []*TreeNode {
22+
routes := make([]*TreeNode, 0)
23+
24+
curr := head
25+
for curr.Val != target.Val {
26+
routes = append(routes, curr)
27+
if target.Val == curr.Val {
28+
break
29+
} else if target.Val < curr.Val {
30+
curr = curr.Left
31+
} else {
32+
curr = curr.Right
33+
}
34+
}
35+
return append(routes, curr)
36+
}
37+
38+
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
39+
pRoutes := getRoutes(root, p)
40+
qRoutes := getRoutes(root, q)
41+
42+
idx := 0
43+
for idx < min(len(pRoutes), len(qRoutes)) && pRoutes[idx] == qRoutes[idx] {
44+
idx++
45+
}
46+
47+
return pRoutes[idx-1]
48+
}
49+
50+
```
51+
# Intuition & Approach
52+
(솔루션의 해결법 참고)
53+
54+
두 노드는 공통조상까지는 동일한 대소관계를 가지고 있다가, 공통 조상 이후로 대소관계가 구분된다.
55+
따라서, 루트에서 동일한 대소관계가 있는 조상까지 이동한다. (다시 말해, 대소 관계가 구분되는 특정 지점이 발생한다면 그 지점의 부모가 공통 조상이다.)
56+
# Complexity
57+
- Time complexity
58+
- 평균: $O(log(n))$
59+
- 최악: $O(n)$
60+
- 트리의 높이만큼 순회를 하게된다. 노드가 n개 이므로, 트리의 높이는 최선 `log(n)`, 최악 `n`이 된다.
61+
62+
- Space complexity: $O(1)$
63+
- 별도 자료구조를 사용하지 않고, 링크드 리스트의 순회만이 존재한다.
64+
65+
# Code
66+
## For-loop
67+
```go
68+
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
69+
curr := root
70+
for {
71+
if p.Val < curr.Val && q.Val < curr.Val {
72+
curr = curr.Left
73+
} else if p.Val > curr.Val && q.Val > curr.Val {
74+
curr = curr.Right
75+
} else {
76+
break
77+
}
78+
}
79+
return curr
80+
}
81+
82+
```
83+
## Recursion
84+
```go
85+
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
86+
if p.Val < root.Val && q.Val < root.Val {
87+
return lowestCommonAncestor(root.Left, p, q)
88+
} else if p.Val > root.Val && q.Val > root.Val {
89+
return lowestCommonAncestor(root.Right, p, q)
90+
}
91+
return root
92+
}
93+
94+
```
95+
: 함수 콜스택이 트리의 높이만큼 증가하므로, 공간 복잡도가 O(n)까지 증가할 수 있다. (처음 해결법의 공간 복잡도처럼.)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Intuition
2+
n번 째를 알기 위해 정보(몇 번째에 어떤 요소가 있는지)를 알아야 한다.
3+
# Approach
4+
1. 배열에 링크드 리스트 정보를 모두 넣는다.
5+
2. 맨 앞 요소를 제거해야하는 경우 (`len(nodes) == n`) 헤드의 다음을 반환한다.
6+
3. 뒤에서 `n`번째 요소(`len(nodes)-n`)의 앞과 뒤를 이어붙여 `n`번째 요소를 제거한다.
7+
# Complexity
8+
- Time complexity: $O(n)$
9+
- 링크드 리스트의 길이 `n`에 대하여, 이를 순회하는 비용이 발생한다.
10+
- Space complexity: $O(n)$
11+
- 링크드 리스트의 길이 `n`에 대하여, 배열의 크기 `n`이 발생한다.
12+
13+
# Code
14+
## Array
15+
```go
16+
func removeNthFromEnd(head *ListNode, n int) *ListNode {
17+
nodes := make([]*ListNode, 0, 100)
18+
19+
for curr := head; curr != nil; curr = curr.Next {
20+
nodes = append(nodes, curr)
21+
}
22+
23+
if len(nodes) == n {
24+
return head.Next
25+
}
26+
27+
nodes[len(nodes)-n-1].Next = nodes[len(nodes)-n].Next
28+
return head
29+
}
30+
31+
```
32+
# Intuition
33+
배열의 모든 원소를 저장할 필요는 없어 보였다. 공간 복잡도를 줄일 방법을 알아보았다.
34+
# Approach
35+
모든 원소가 아니라, `n`번째의 이전(`prev`), 이후 (`next`)만을 저장하게 하였다.
36+
# Complexity
37+
- Time complexity: $O(n)$
38+
- 링크드 리스트의 길이 `n`에 대하여, 이를 순회하는 비용이 발생한다.
39+
- Space complexity: $O(1)$
40+
- 2개의 요소만 유지하므로, `O(1)`이다.
41+
42+
# Code
43+
## Two Pointer
44+
```go
45+
func removeNthFromEnd(head *ListNode, n int) *ListNode {
46+
len := 0
47+
for curr := head; curr != nil; curr = curr.Next {
48+
len++
49+
}
50+
51+
if len == n {
52+
return head.Next
53+
}
54+
55+
var prev, curr, next *ListNode
56+
curr = head
57+
next = head.Next
58+
for i := 0; i+n < len; i++ {
59+
prev = curr
60+
curr = next
61+
next = next.Next
62+
}
63+
64+
prev.Next = next
65+
return head
66+
}
67+
68+
```

reorder-list/invidam.go.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Intuition
2+
<!-- Describe your first thoughts on how to solve this problem. -->
3+
재귀함수를 이용한 풀이를 생각하였으나, 맨 뒤에 있는 요소가 2번째 까지 장거리 이동해야 하므로 맨 뒤를 `O(1)`에 접근하기 위한 방법(배열을 떠올림)이 필요할 것이다.
4+
<!-- Describe your approach to solving the problem. -->
5+
1. Deque안에 모든 노드들을 삽입한다.
6+
- 코드에서는 배열(`nodes`)와 두 인덱스(`i`, `j`)가 `Deque`의 역할을 대신한다.
7+
2. 맨 앞, 맨 뒤에서 하나씩을 뺀다. (`f`, `b`)
8+
3. 맨 앞 요소 뒤에 맨 뒤 요소를 넣는다. (`f` --> `b` -- (기존) `f.Next`)
9+
3. reorder이후 맨 마지막 요소에 `nil`을 추가해 종료 지점을 만든다.
10+
# Complexity
11+
- Time complexity: $O(n)$
12+
- 링크드 리스트의 길이 `n`에 대하여, 링크드리스트 순회와 배열 순회 비용 `n`이 발생한다.
13+
- Space complexity: $O(n)$
14+
- 링크드 리스트의 길이 `n`에 대하여, 배열(`nodes`) 생성에 비용 `n`이 발생한다.
15+
# Code
16+
## Two Pointer(Deque)
17+
```go
18+
func reorderListv1(head *ListNode) {
19+
nodes := make([]*ListNode, 0, 25)
20+
for curr := head; curr != nil; curr = curr.Next {
21+
nodes = append(nodes, curr)
22+
}
23+
24+
i, j := 0, len(nodes)-1
25+
for i < j {
26+
nodes[j].Next = nodes[i].Next
27+
nodes[i].Next = nodes[j]
28+
29+
i++
30+
j--
31+
}
32+
nodes[i].Next = nil
33+
}
34+
35+
```
36+
37+
## 다른 풀이
38+
: 솔루션을 보며 뒤쪽 절반을 `reverse()`하여 해결하는 솔루션이 더욱 직관적으로 느껴졌다.
39+
40+
```go
41+
func reverse(node *ListNode) *ListNode {
42+
var prev *ListNode
43+
curr := node
44+
for curr != nil {
45+
next := curr.Next
46+
curr.Next = prev
47+
prev = curr
48+
49+
curr = next
50+
}
51+
return prev
52+
}
53+
54+
func reorderList(head *ListNode) {
55+
slow, fast := head, head
56+
for fast != nil && fast.Next != nil {
57+
fast = fast.Next.Next
58+
slow = slow.Next
59+
}
60+
61+
curr, rCurr := head, reverse(slow.Next)
62+
slow.Next = nil
63+
for curr != nil && rCurr != nil {
64+
next, rNext := curr.Next, rCurr.Next
65+
66+
rCurr.Next = next
67+
curr.Next = rCurr
68+
69+
curr, rCurr = next, rNext
70+
}
71+
}
72+
73+
```
74+
: 인덱스 조절하는 게 꽤나 어려워서 솔루션을 참고했다...

0 commit comments

Comments
 (0)