From 149effb4dc1fe69ead96e2987872914a8512dcf1 Mon Sep 17 00:00:00 2001 From: Invidam Date: Thu, 9 May 2024 16:14:26 +0900 Subject: [PATCH] feat(invidam): week 2 update. --- invert-binary-tree/invidam.go.md | 91 +++++++++++++++++++++ linked-list-cycle/invidam.go.md | 66 +++++++++++++++ merge-two-sorted-lists/invidam.go.md | 116 +++++++++++++++++++++++++++ reverse-linked-list/invidam.go.md | 61 ++++++++++++++ valid-parentheses/invidam.go.md | 102 +++++++++++++++++++++++ 5 files changed, 436 insertions(+) create mode 100644 invert-binary-tree/invidam.go.md create mode 100644 linked-list-cycle/invidam.go.md create mode 100644 merge-two-sorted-lists/invidam.go.md create mode 100644 reverse-linked-list/invidam.go.md create mode 100644 valid-parentheses/invidam.go.md diff --git a/invert-binary-tree/invidam.go.md b/invert-binary-tree/invidam.go.md new file mode 100644 index 000000000..86060e41b --- /dev/null +++ b/invert-binary-tree/invidam.go.md @@ -0,0 +1,91 @@ +# Intuition + +Use DFS, referencing the property of a tree (i.e. child node is the root node of a subtree.) +# Approach + +1. Visit the root node. +2. If visited node is `nil`, return `nil` +3. Swap left and right nodes. +4. Visit Swapped left and right nodes. +5. Repeat Step 2 ~ 4. +# Complexity +- Time complexity: $$O(n)$$ + + +- Space complexity: $$O(n)$$ + + +# Code +``` +func invertTree(root *TreeNode) *TreeNode { + if root == nil { + return nil + } + root.Left, root.Right = invertTree(root.Right), invertTree(root.Left) + return root +} +``` +- - - +# Intuition + +Visit, But Use BFS. (`for loop`) +# Approach + +1. Create Queue and push root node to it. +2. If the queue is empty, return the `root` node. +3. Otherwise, pop the top node. +4. Swap the left and right children of the removed node. +5. Push swapped children. +4. Repeat Step +# Complexity +- Time complexity: $$O(n)$$ + + +- Space complexity: $$O(n)$$ + + +# Code +``` +type Queue[T any] struct { + Index int + Nodes []T +} + +func NewQueue[T any]() Queue[T] { + return Queue[T]{Nodes: make([]T, 0)} +} + +func (q *Queue[T]) Push(node T) { + q.Nodes = append(q.Nodes, node) +} + +func (q *Queue[T]) Pop() T { + ret := q.Nodes[q.Index] + q.Index++ + return ret +} + +func (q *Queue[T]) IsEmpty() bool { + return q.Index == len(q.Nodes) +} + +func invertTree(root *TreeNode) *TreeNode { + q := NewQueue[*TreeNode]() + q.Push(root) + for !q.IsEmpty() { + t := q.Pop() + if t == nil { + continue + } + t.Left, t.Right = t.Right, t.Left + + q.Push(t.Left) + q.Push(t.Right) + } + return root +} +``` + +# Learned +- 고언어에서 `a, b = b, a` 처럼 간결한 코딩이 가능하다. +- 포인터 타입과 일반 타입의 차이 (고언어에서는 일반 타입을 넘길 시 무조건 복사한다.) \ No newline at end of file diff --git a/linked-list-cycle/invidam.go.md b/linked-list-cycle/invidam.go.md new file mode 100644 index 000000000..30cbd0906 --- /dev/null +++ b/linked-list-cycle/invidam.go.md @@ -0,0 +1,66 @@ +# Intuition + +Makr elements as visited by setting a dirty bit. +Check Dirty bit to visited nodes. +# Approach + +1. Initiate a constant value named `visited` over the input range. (In my case `-10001`.) +2. Start Visitng node. by head. +3. Check if the current node is `nil` or its value matches the visited value (Initially, the head node should not be visited value) +4. If node is valid(i.e. not `nil` and not `visited`), set the node's value as the visited value. +5. Move to the next node and repeat step 3. + +# Complexity +- Time complexity: $$O(n)$$ + + + +- Space complexity: $$O(n)$$ + +(n = list's size) +# Code +``` +const visited = -10001 +func hasCycle(head *ListNode) bool { + if head == nil { + return false; + } + if head.Val == visited { + return true; + } + head.Val = visited + return hasCycle(head.Next) +} +``` + +- - - +# Institution +Use "The tortoise and hare" Algorithom. +# Approach + +1. Designate two node to move at differnt speeds. One node should move faster (referred to as `fast`), and the other should move slower (`slow`) +2. Allow both nodes to iterate through the graph, with each node moving at its designated speed. +3. If the `fast` node catches up to the `slow` node (i.e. both node points to the same node at same point), the the graph contains a cycle.is cycle. +# Complexity +- Time complexity: $$O(n)$$ + +- Space complexity: $$O(1)$$ + +# Code +``` +func hasCycle(head *ListNode) bool { + if head == nil || head.Next == nil { + return false + } + for slow, fast := head, head.Next; fast != nil && fast.Next != nil; slow, fast = slow.Next, fast.Next.Next{ + if slow == fast { + return true + } + } + return false +} + +``` +# Learned +- 약한 부분이었던 투포인터 알고리즘에 대해 좀 더 생각해보았다. +- 반복문을 깔끔하게 작성하는 법을 고민해보았다. \ No newline at end of file diff --git a/merge-two-sorted-lists/invidam.go.md b/merge-two-sorted-lists/invidam.go.md new file mode 100644 index 000000000..160fa0791 --- /dev/null +++ b/merge-two-sorted-lists/invidam.go.md @@ -0,0 +1,116 @@ +# Intuition + +링크드리스트 --> 반복문 or 재귀호출이 떠올랐다. +# Approach + +- 재귀 호출과 반복문 사이에서 고민하였다. +- 새로운 노드를 만들고, 작은 노드들을 이어 붙였다. (V1_1, V2_1) +- 새로운 노드의 생성 없이, 기존 노드들을 연결하여 해결할 수도 있었다. (V1_2, V2_2) +# Complexity +- Time complexity: $$O(N+M)$$ + + +- Space complexity: $$O(N+M)$$ + + +(N,M은 각각 두 링크드리스트의 길이) +# Code +``` +func mergeTwoListsV1(list1 *ListNode, list2 *ListNode) *ListNode { + if list1 == nil && list2 == nil { + return nil + } else if list1 != nil { + return list1 + } else if list2 != nil { + return list2 + } + // assert list1, list2 is not nil + var val int + if list1.Val < list2.Val { + val = list1.Val + list1 = list1.Next + } else { + val = list2.Val + list2 = list2.Next + } + + return &ListNode{Val: val, Next: mergeTwoListsV1(list1, list2)} +} + +func mergeTwoListsV1_2(list1 *ListNode, list2 *ListNode) *ListNode { + if list1 == nil { + return list2 + } else if list2 == nil { + return list1 + } + + if list1.Val < list2.Val { + list1.Next = mergeTwoListsV1_2(list1.Next, list2) + return list1 + } else { + list2.Next = mergeTwoListsV1_2(list1, list2.Next) + return list2 + } +} + +func mergeTwoListsV2_1(list1 *ListNode, list2 *ListNode) *ListNode { + var head = &ListNode{} + curr := head + + for list1 != nil && list2 != nil { + if list1.Val < list2.Val { + curr.Next = list1 + list1 = list1.Next + } else { + curr.Next = list2 + list2 = list2.Next + } + curr = curr.Next + } + + if list1 != nil { + curr.Next = list1 + } else if list2 != nil { + curr.Next = list2 + } + + return head.Next +} + +func mergeTwoListsV2_2(list1 *ListNode, list2 *ListNode) *ListNode { + if list1 == nil { + return list2 + } else if list2 == nil { + return list1 + } + + head := list1 + if list1.Val > list2.Val { + head = list2 + list2 = list2.Next + } else { + list1 = list1.Next + } + curr := head + + for list1 != nil && list2 != nil { + if list1.Val < list2.Val { + curr.Next = list1 + list1 = list1.Next + } else { + curr.Next = list2 + list2 = list2.Next + } + curr = curr.Next + } + + if list1 != nil { + curr.Next = list1 + } else if list2 != nil { + curr.Next = list2 + } + + return head +} + +``` \ No newline at end of file diff --git a/reverse-linked-list/invidam.go.md b/reverse-linked-list/invidam.go.md new file mode 100644 index 000000000..b0fc217f8 --- /dev/null +++ b/reverse-linked-list/invidam.go.md @@ -0,0 +1,61 @@ +# Intuition +두 변수간의 Swap이 떠올랐다. + +# Approach + +- 링크드 리스트의 "언제나 자신의 값과 다음 노드의 주소를 유지한다"는 특성이 재귀 함수를 유용하게 쓸 수 있다고 판단했다. +# Complexity +- Time complexity: $$O(n)$$ + + +- Space complexity: $$O(n)$$ + + +(N은 링크드리스트의 길이) +*/ + +# Code +- V1은 재귀호출, V2는 반복문 활용을 한 경우이다. +- V3은 V1을 개선하여, 하나의 함수만으로 간결하게 해결했다. +``` + +func reverseListV3(head *ListNode) *ListNode { + if head == nil || head.Next == nil { + return head + } + + newHead := reverseList(head.Next) + + head.Next.Next = head + head.Next = nil + + return newHead +} + +func reverseListV2(head *ListNode) *ListNode { + var prev *ListNode + curr := head + for curr != nil { + next := curr.Next + curr.Next = prev + prev = curr + curr = next + } + return prev +} + +func reverseNodeAllV1(head *ListNode, prev *ListNode) *ListNode { + if head == nil { + return prev + } + next := head.Next + head.Next = prev + return reverseNodeAllV1(next, head) +} +func reverseListV1(head *ListNode) *ListNode { + return reverseNodeAllV1(head, nil) +} +``` + +# Remind +재귀 함수의 특성(자기 자신을 호출한 이후 이전의 코드를 실행할 수 있다는 점)을 활용해 간결한 코딩이 가능하다. \ No newline at end of file diff --git a/valid-parentheses/invidam.go.md b/valid-parentheses/invidam.go.md new file mode 100644 index 000000000..10aed90c1 --- /dev/null +++ b/valid-parentheses/invidam.go.md @@ -0,0 +1,102 @@ +# Intuition +스택을 이용하여 이전에 등장한 여는 괄호를 판단한다. + +# Approach + +1. 기존에 이미 알고있던 문제라서 스택을 이용한 풀이가 떠올랐다. +2. 스택을 구현하기에 귀찮음이 있어 등장한 괄호의 빈도를 저장하면 어떨까 싶었다. (관련 문제가 생각났다.) +3. 2번의 경우 여러 종류를 대응할 수 없어 (등장 빈도는 같지만 순서가다른 `([{]})` 같은 경우) 스택을 이용해 해결했다. +# Complexity +- Time complexity: $$O(n)$$ + + +- Space complexity: $$O(n)$$ + +(n은 문자열의 길이) +# Code +``` + + +type Stack[T any] struct { + Data []T + Index int +} + +func CreateStack[T any](capacity int) Stack[T] { + return Stack[T]{Data: make([]T, 0, capacity), Index: 0} +} + +// Push adds an element to the queue, expanding it if necessary. +func (q *Stack[T]) Push(value T) { + if q.Index >= len(q.Data) { + q.Data = append(q.Data, value) + } else { + q.Data[q.Index] = value + } + q.Index++ +} + +func (q *Stack[T]) Pop() T { + if q.Index-1 < 0 { + panic("Stack is empty") + } + element := q.Data[q.Index-1] + q.Index-- + return element +} + +func (q *Stack[T]) Peek() T { + if q.Index-1 < 0 { + panic("Stack is empty") + } + return q.Data[q.Index-1] +} + +func (q *Stack[T]) Size() int { + return q.Index +} + +func getOpenBracket(close rune) uint8 { + opens := "([{" + closes := ")]}" + + for i, ch := range closes { + if ch == close { + return opens[i] + } + } + + return ' ' +} +func isValid(s string) bool { + opens := "([{" + + savedStack := CreateStack[uint8](len(s)) + + for _, ch := range s { + if strings.ContainsRune(opens, ch) { + savedStack.Push(uint8(ch)) + continue + } + + if savedStack.Size() == 0 { + return false + } + + open := getOpenBracket(ch) + if savedStack.Peek() != open { + return false + } + savedStack.Pop() + } + + return savedStack.Size() == 0 +} + +``` + +# Learned +- append, copy 등의 내장 함수: append는 배열 용량 초과시 resize까지 진행하며, resize의 크기는 os에 따라 다르다. +- mapping을 간단히 하는 방법.: 기존에는 배열을 이용하여 매핑했으나, hash map을 이용하는 게 직관적이라 느꼈다. +- 제너릭 활용: Go의 `type parameter`는 다른 언어(e.g. `Java`)의 제너릭과 인터페이스(다형성 활용)의 교집합 느낌이다. +- Go에서 문자를 처리하는 방법: 어디서는 `rune`이고 어디선 `uint8`이다. \ No newline at end of file