diff --git a/container-with-most-water/invidam.go.md b/container-with-most-water/invidam.go.md new file mode 100644 index 000000000..691496783 --- /dev/null +++ b/container-with-most-water/invidam.go.md @@ -0,0 +1,105 @@ +# Intuition +높이가 양 끝 라인에 의해 결정된다는 것을 응용하여 투포인터 해결법을 고안했다. + +# Approach +1. 높이(`h`)를 0 ~ 최대까지 순회한다. +2. 높이를 초과하는 선까지 양 끝(`l`, `r`)을 조절한다. +3. 조절한 양 끝을 최댓값 갱신에 이용한다. + +# Complexity +- Time complexity: $O(n)$ + - 배열의 크기 `n`에 대하여, 반복문이 배열의 인덱스를 모두 순회하면 종료되기에 (`l < r`이 깨질 때) 이에 따라 시간복잡도가 결정된다. +- Space complexity: $O(n), inline$ + - 배열의 크기 `n`에 대하여, 입력값으로 배열을 받으니 inline인 n이 소요된다. + +# Code +## Two Pointer +```go +func maxArea(height []int) int { + l, r := 0, len(height)-1 + var maxArea int + for l < r { + minH := min(height[l], height[r]) + maxArea = max(minH*(r-l), maxArea) + + if minH == height[l] { + l++ + } else { + r-- + } + } + + return maxArea +} + +``` +: 원래는 deque를 이용해 해결했는데, 솔루션의 투포인터를 이용한 것과 동일한 방식이어서 깔끔한 후자로 수정했다. + +## Deque +```go +type Deque struct { + Nodes []int +} + +func NewDeque(arr []int) Deque { + return Deque{Nodes: arr} +} + +func (dq *Deque) PushFront(node int) { + dq.Nodes = append([]int{node}, dq.Nodes...) +} + +func (dq *Deque) PushBack(node int) { + dq.Nodes = append(dq.Nodes, node) +} +func (dq *Deque) Front() int { + return dq.Nodes[0] +} + +func (dq *Deque) Back() int { + return dq.Nodes[len(dq.Nodes)-1] +} + +func (dq *Deque) PopFront() int { + ret := dq.Front() + + dq.Nodes = dq.Nodes[1:] + + return ret +} + +func (dq *Deque) PopBack() int { + ret := dq.Back() + + dq.Nodes = dq.Nodes[0 : len(dq.Nodes)-1] + + return ret +} + +func (dq *Deque) Size() int { + return len(dq.Nodes) +} + +func maxArea(height []int) int { + dq := NewDeque(height) + + var max int + for h := 0; dq.Size() > 1; h++ { + for dq.Size() != 0 && dq.Front() < h { + dq.PopFront() + } + for dq.Size() != 0 && dq.Back() < h { + dq.PopBack() + } + + area := h * (dq.Size() - 1) + + if area > max { + max = area + } + } + + return max +} + +``` \ No newline at end of file diff --git a/find-minimum-in-rotated-sorted-array/invidam.go.md b/find-minimum-in-rotated-sorted-array/invidam.go.md new file mode 100644 index 000000000..ef0cfd34d --- /dev/null +++ b/find-minimum-in-rotated-sorted-array/invidam.go.md @@ -0,0 +1,44 @@ +# Intuition +전형적인 이분탐색 문제였다. + +# Approach +1. `nums[0]보다 작은지 여부`를 검사하도록 설계했다. +2. `lo`는 항상 F이며, `hi`는 항상 T이다. +2. 배열의 원소들에 대해 FFFFFFFTTTTTT가 되는데, 이 때 `T`가 처음 등장하는 지점을 찾는다. +3. 등장하지 않는 경우 (`nums[0]`이 최소인 경우 = 회전이 없는 경우)는 hi가 배열 범위 밖이므로, 인덱스 0을 반환하게 했다. + +(문제 해결 이후 타 문제 재활용을 위해 인덱스를 찾도록 리팩터링했다.) +# Complexity +- Time complexity: $O(log(n))$ + - 배열의 길이 `n`에 대하여, 범위를 반으로 줄여가며 이분 탐색하므로 `log(n)`이 발생한다. + +- Space complexity: $O(n), inline$ + - 배열의 길이 `n`에 대하여, 입력(`nums`)의 비용이 존재한다. + +# Code +```go +func findMinIdx(nums []int) int { + lo, hi := -1, len(nums) + for lo+1 < hi { + mid := (lo + hi) / 2 + + if nums[mid] < nums[0] { + hi = mid + } else { + lo = mid + } + } + if hi == len(nums) { + return 0 + } + return hi +} + +func findMin(nums []int) int { + return nums[findMinIdx(nums)] +} + +``` + +# 여담 +이분탐색 문제는 타 솔루션을 참고하지 않는 편이다. 왜냐하면 `lo`, `hi`, 종료 조건, 반환 값 설정이 비슷해보이지만 엄청 다른 의미이기에 큰 도움이 안된다고 생각한다. \ No newline at end of file diff --git a/longest-repeating-character-replacement/invidam.go.md b/longest-repeating-character-replacement/invidam.go.md new file mode 100644 index 000000000..585f0c143 --- /dev/null +++ b/longest-repeating-character-replacement/invidam.go.md @@ -0,0 +1,46 @@ +# Intuition +문자열 내에 영문자들만 등장한다는 것에서 DP, 그리디가 아니라 빈도수 계산 + 투포인터임을 알게되었다. + +# Approach +1. A-Z에 대해 순회한다. +2. 포함하는 배열이 커지며(`j++`) 순회하는 문자가 몇 번 연속해서 등장하는지 계산한다. (`have`) +3. 연속하지 않은 문자가 등장하더라도 `k`번은 봐준다. +4. `k`번이상이라 봐줄 수 없는 경우 (`have+k < j-i+1`인 경우) 포함하는 배열을 줄인다. (`i++`) + +# Complexity +- Time complexity: $O(n)$ + - 순회하는 문자들은 상수 개수(26)이므로 무시한다. + - 문자열의 길이 `n`에 대하여, 이를 순회하는 비용이 든다. (`j + +# Code +```go +func lengthOfLongestSubstring(s string) int { + if len(s) == 0 { + return 0 + } + contains := make(map[uint8]bool) + + l, r := 0, 0 + maxLen := 1 + + for r < len(s) { + for contains[s[r]] { + contains[s[l]] = false + l++ + } + contains[s[r]] = true + maxLen = max(maxLen, r-l+1) + r++ + } + + return maxLen +} + +``` \ No newline at end of file diff --git a/search-in-rotated-sorted-array/invidam.go.md b/search-in-rotated-sorted-array/invidam.go.md new file mode 100644 index 000000000..9ecdc3f99 --- /dev/null +++ b/search-in-rotated-sorted-array/invidam.go.md @@ -0,0 +1,74 @@ +# Intuition +이분 탐색인 게 직관적으로 보였다. + +# Approach +1. 찾으려는 값(`target`)이 최솟값의 인덱스(`minIdx`) 이전/이후인지 판단한다. (`target < nums[0] || minIdx == 0`) +2. 판단한 범위(이전, 이후)에 대해서만 배열을 잘라 `lowerBound`를 실행한다. +3. 결과를 통해 타겟의 인덱스(`targetIdx`)를 획득한다. +4. 예외처리(범위 벗어난 경우, `lowerBound`은 맞으나 일치하진 않는 경우)엔 `-`을 아닌 경우는 인덱스를 반환한다. + +# Complexity +- Time complexity: $O(log(n))$ + - 배열의 길이 `n`에 대하여, 범위를 반으로 줄여가며 이분 탐색하므로 `log(n)`이 발생한다. + - 두 이분탐색 함수를 사용하긴 하지만, 합연산이므로 복잡도에는 영향이 없다. +- Space complexity: $O(n), inline$ + - 배열의 길이 `n`에 대하여, 입력(`nums`)의 비용이 존재한다. + +# Code +```go +func findMinIdx(nums []int) int { + lo, hi := -1, len(nums) + for lo+1 < hi { + mid := (lo + hi) / 2 + + if nums[mid] < nums[0] { + hi = mid + } else { + lo = mid + } + } + if hi == len(nums) { + return 0 + } + return hi +} + +func lowerBound(nums []int, target int) int { + lo, hi := -1, len(nums) + for lo+1 < hi { + mid := (lo + hi) / 2 + + if nums[mid] < target { + lo = mid + } else { + hi = mid + } + } + return hi +} + +func search(nums []int, target int) int { + minIdx := findMinIdx(nums) + var targetIdx int + if target < nums[0] || minIdx == 0 { + targetIdx = lowerBound(nums[minIdx:], target) + minIdx + } else { + targetIdx = lowerBound(nums[0:minIdx], target) + } + + if len(nums) <= targetIdx || target != nums[targetIdx] { + return -1 + } + return targetIdx + +} + +``` +# I learned +`whitespace around operator`(연산자 근처에서 공백 사용)에 대한 golang의 해석이 일반적인 언어들과 다른 걸 알게되었다. +- 일반언어: `lo + 1 < hi` +- GoLang: `lo+1 < hi` + +즉, 모든 연산자 근처에 공백을 추가하는 것이 아니라 낮은 우선순위의 연산자에 대해서만 추가한다. + +참고: https://news.ycombinator.com/item?id=10796427 \ No newline at end of file