-
-
Notifications
You must be signed in to change notification settings - Fork 195
[Invidam] Week 06 Solutions #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
|
||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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`)의 비용이 존재한다. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 문제도 입력값에 대해서는 고려하지 않아도 될 것 같습니다. |
||
|
||
# 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`, 종료 조건, 반환 값 설정이 비슷해보이지만 엄청 다른 의미이기에 큰 도움이 안된다고 생각한다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<len(s)`) | ||
|
||
- Space complexity: $O(n) inline$ | ||
- 문자열의 길이 `n`에 대하여, 입력으로 받은 `s`만큼 비용이 든다. | ||
|
||
# Code | ||
```go | ||
func characterReplacement(s string, k int) int { | ||
var maxLen int | ||
for ch := 'A'; ch <= 'Z'; ch++ { | ||
bky373 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
i, j := 0, 0 | ||
var have int | ||
for j < len(s) { | ||
if ch == rune(s[j]) { | ||
have++ | ||
} | ||
for have+k < j-i+1 { | ||
if ch == rune(s[i]) { | ||
have-- | ||
} | ||
i++ | ||
} | ||
maxLen = max(maxLen, j-i+1) | ||
j++ | ||
} | ||
} | ||
return maxLen | ||
} | ||
|
||
``` | ||
# 여담 | ||
- 가장 많이 등장한 문자를 검색하는 솔루션을 확인했다. 동일 문제를 다르게 접근한다는 게 신기했다. | ||
- 비교해보자면, A~Z중 한 문자가 등장하는 것만 계산한다는 본인의 풀이가 더욱 직관적이라는 생각이 들었다. | ||
- 다만, 코드로 나타냈을 때는 덜 직관적이었다...; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Intuition | ||
DP, 그리디 등을 시도하려다가 오류를 발견했다. 화이트보드에 예제 문제들을 손으로 풀어보니 직관적으로 떠올랐다. | ||
# Approach | ||
1. 똑같은 음식을 먹지 않는 지렁이를 생각하자. | ||
2. 다음 음식을 이미 먹었다면, 먹었던 걸 뱉는다. (`l++`) | ||
3. 다음 음식을 먹지 않았다면, 먹는다. (`r++`) | ||
4. 지렁이의 길이를 최댓값 갱신에 활용한다. | ||
# Complexity | ||
- Time complexity: $O(n)$ | ||
- 문자열의 길이 `n`에 대하여, 이를 순회하는 비용이 소모된다. | ||
- Space complexity: $O(n)$ | ||
- 문자열의 길이 `n`에 대하여, 등장 여부를 저장하는 자료구조(`contains`)는 최대 `n`개 만큼을 저장할 수 있으므로 `n`이다. | ||
<!-- Add your space complexity here, e.g. $$O(n)$$ --> | ||
|
||
# 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 | ||
} | ||
|
||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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`)의 비용이 존재한다. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 입력값에 대해서는 고려하지 않아도 되지 않을까 싶네요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 다음부턴 의견 반영해보겠습니다! |
||
|
||
# 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (사소) 사용하신 이분탐색은 결국 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 생각해보지 못했는데 그것도 좋은 아이디어인 것 같네요! 저도 자세히 아는 건 아니지만, 매번 불필요하게 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
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 | ||
bky373 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} 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 |
Uh oh!
There was an error while loading. Please reload this page.