Skip to content
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

[Flynn] Week 09 #520

Merged
merged 7 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions find-minimum-in-rotated-sorted-array/flynn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
풀이 1
- 주어진 배열을 두 부분으로 나눌 수 있기 때문에 이진탐색을 이용하여 풀이할 수 있습니다
주어진 배열이 A = [4,5,6,7,0,1,2] 라고 할 때, 이 배열은 두 부분으로 나눌 수 있습니다
A[0:3] : rotate되어 앞으로 배열의 앞으로 옮겨진 부분
A[4:6] : rotate되어 배열의 뒤로 밀려난 부분
이걸 조금 다르게 표현하면 이렇게도 바라볼 수 있습니다
f(A) = [T, T, T, T, F, F, F] (T/F: rotate되어 배열의 앞으로 옮겨진 부분인가?)

이 때, 우리가 찾는 값 (the Minimum in the rotated sorted array)는 두 구간의 경계에 위치하고 있습니다
즉, 첫번째 F의 위치를 찾는 문제로 바꿔 생각할 수 있단 뜻입니다

Big O
- N: 주어진 배열 nums의 길이

- Time complexity: O(logN)
- Space complexity: O(1)
*/

func findMin(nums []int) int {
lo, hi := 0, len(nums)-1
// rotate가 0번 실행된 경우, 이진탐색을 실행할 필요가 없고 이진탐색의 초기값 설정이 까다로워지기 때문에 처음에 따로 처리해줍니다
// 앞서 hi의 초기값을 설정할 때, len(nums)가 아닌 len(nums) - 1로 설정할 수 있었던 이유이기도 합니다
if nums[lo] < nums[hi] {
return nums[lo]
}

// Go는 while문에 대응하는 표현을 for로 이렇게 표현합니다
for lo < hi {
mid := lo + (hi-lo)/2

// 만일 mid가 앞으로 밀려난 부분에 속한다면...
if nums[lo] <= nums[mid] && nums[mid] > nums[hi] {
lo = mid + 1
} else {
hi = mid
}
}

return nums[lo]
}

/*
풀이 2
- 풀이 1에서 덜어낼 수 있는 로직을 덜어낸 좀 더 깔끔한 이진탐색 풀이입니다

Big O
- 풀이 1과 동일
*/

func findMin(nums []int) int {
lo, hi := 0, len(nums)

for lo < hi {
mid := lo+(hi-lo)/2

if nums[mid] > nums[len(nums)-1] {
lo = mid + 1
} else {
hi = mid
}
}

return nums[lo]
}
51 changes: 51 additions & 0 deletions linked-list-cycle/flynn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* 풀이
* - Floyd's Tortoise and Hare Algorithm을 이용한 풀이입니다.
* 참고: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare
*
* Big O
* - N: 노드의 개수
* - L: 루프 구간에 속하는 노드의 개수
*
* Time Complexity: O(N)
* - 루프가 없는 경우:
* - fast 포인터가 링크드리스트의 끝까지 이동하면 종료합니다.
* - 이 때 fast 포인터의 탐색 시간 복잡도는 다음과 같습니다:
* O(N / 2) = O(N)
* - 루프가 있는 경우:
* - slow 포인터와 fast 포인터가 루프 안에서 만나면 종료합니다.
* - 이 때 slow 포인터의 탐색 시간 복잡도는 다음과 같습니다:
* O((N - L) + L * c) (c는 slow가 fast를 만날 때까지 루프를 반복한 횟수)
* = O(r + (N - r) * c) (L은 0 <= r <= N인 r에 대해 N - r로 표현할 수 있습니다)
* = O(N)
*
* Space Complexity: O(1)
* - 노드의 개수에 상관없이 일정한 공간을 사용합니다.
*/

/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func hasCycle(head *ListNode) bool {
if head == nil {
return false
}

slow := head
fast := head

for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next

if slow == fast {
return true
}
}

return false
}
60 changes: 60 additions & 0 deletions maximum-subarray/flynn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
풀이 1
- 아래와 같은 memo 배열을 만들어서 풀이할 수 있습니다 (참고: Kadane's Algorithm)
memo[i] = nums[:i] 중에서 nums[i]를 반드시 포함하는 부분 배열의 최대 합

Big O
- N: 주어진 배열 nums의 길이
- Time complexity: O(N)
- Space complexity: O(N)
*/

func maxSubArray(nums []int) int {
n := len(nums)

memo := make([]int, n)
copy(memo, nums)

maxSum := nums[0]

for i := 1; i < n; i++ {
if memo[i-1] > 0 {
memo[i] += memo[i-1]
}

if maxSum < memo[i] {
maxSum = memo[i]
}
}

return maxSum
}

/*
풀이 2
- 알고리즘의 전개 과정을 보면 O(N)의 공간복잡도를 갖는 memo가 필요하지 않다는 걸 알 수 있습니다
- memo 배열 대신 현재 계산 중인 부분 배열의 합만 계속 갱신합니다

Big O
- N: 주어진 배열 nums의 길이
- Time complexity: O(N)
- Space complexity: O(1)
*/

func maxSubArray(nums []int) int {
maxSum, currSum := nums[0], nums[0]

for i := 1; i < len(nums); i++ {
if currSum > 0 {
currSum += nums[i]
} else {
currSum = nums[i]
}

if maxSum < currSum {
maxSum = currSum
}
}

return maxSum
}
54 changes: 54 additions & 0 deletions minimum-window-substring/flynn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
풀이
- 슬라이딩 윈도우를 이용하여 풀이합니다
- 주어진 문자열 t의 각 문자와 그 개수를 tMap이라는 해시맵에 저장합니다
- 현재 슬라이딩 윈도우에 포함된 문자와 그 개수를 sMap이라는 해시맵에 저장합니다
- 슬라이딩 윈도우의 끝단(end)을 넓혀나가면서 sMap과 tMap을 비교합니다
이 때 슬라이딩 윈도우가 t의 모든 문자를 포함하는 경우를 찾으면 시작단(start)을 좁혀주면서 슬라이딩 윈도우의 최소폭을 찾습니다
슬라이딩 윈도우가 t의 모든 문자를 포함하는지를 판별하기 위해서 match라는 변수를 사용합니다
match는 sMap[c] == tMap[c]인 문자 c의 개수이며, match == len(tMap) 즉 match가 t의 고유 문자 개수와 같다면 슬라이딩 윈도우가 t의 모든 문자를 포함하는 것이라고 볼 수 있습니다
슬라이딩 윈도우를 좁혀줄 때에도 match의 감소 여부를 잘 관찰하며 최소폭을 갱신합니다
슬라이딩 윈도우를 줄일만큼 줄였다면 다시 슬라이딩 윈도우의 끝단을 넓혀나가면서 다른 경우의 수를 찾습니다

Big O
- M: 주어진 문자열 s의 길이
- N: 주어진 문자열 t의 길이
- Time complexity: O(M + N)
- 문자열 s를 순회하는 반복문 내에서 각 문자는 최대 두 번 조회될 수 있습니다 (start, end) -> O(2M)
- 문자열 t로 해시맵을 만드는 데에는 O(N)의 시간복잡도가 소요됩니다
- O(2M + N) -> O(M + N)
- Space complexity: O(M + N)
- 두 개의 해시맵을 사용하므로 O(M + N)의 공간복잡도를 갖습니다
*/

func minWindow(s string, t string) string {
sMap, tMap := map[string]int{}, map[string]int{}
for _, r := range t {
tMap[string(r)]++
}

start, end, matched, res := 0, 0, 0, ""
for end < len(s) {
sMap[string(s[end])]++

if sMap[string(s[end])] == tMap[string(s[end])] {
matched++
}

for matched == len(tMap) {
if res == "" || len(res) > end-start+1 {
res = s[start : end+1]
}

if sMap[string(s[start])] == tMap[string(s[start])] {
matched--
}
sMap[string(s[start])]--
start++
}

end++
}

return res
}
101 changes: 101 additions & 0 deletions pacific-atlantic-water-flow/flynn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
풀이
- BFS를 이용하여 풀이할 수 있습니다
- 주어진 배열의 가장자리에서부터 시작하는 BFS를 pacific과 atlantic에 대해 각각 실행합니다

Big O
- M: 주어진 배열의 행의 개수
- N: 주어진 배열의 열의 개수
- Time complexity: O(MN)
- 탐색을 진행하는 회수는 배열의 원소 개수에 비례하여 증가합니다
- Space complexity: O(MN)
- queue의 최대 크기는 배열의 원소 개수에 비례하여 증가합니다
- memo 배열의 크기는 배열의 원소 개수에 비례하여 증가합니다
*/

type pair struct {
pacific bool
atlantic bool
}

func pacificAtlantic(heights [][]int) [][]int {
var res [][]int

m, n := len(heights), len(heights[0])

dr := []int{0, 0, 1, -1}
dc := []int{1, -1, 0, 0}

// 모든 r, c에 대해 memo[r][c] = {pacific: false, atlantic: false}로 초기화합니다
memo := make([][]pair, m)
for r := range memo {
memo[r] = make([]pair, n)
}

queue := make([][]int, 0)

// pacific에서 시작하는 BFS
for c := 0; c < n; c++ {
queue = append(queue, []int{0, c})
}
for r := 1; r < m; r++ {
queue = append(queue, []int{r, 0})
}

for len(queue) > 0 {
r, c := queue[0][0], queue[0][1]
queue = queue[1:]

memo[r][c].pacific = true

for i := 0; i < 4; i++ {
nr, nc := r+dr[i], c+dc[i]

if !(0 <= nr && nr < m && 0 <= nc && nc < n) {
continue
}

if heights[r][c] <= heights[nr][nc] && !memo[nr][nc].pacific {
queue = append(queue, []int{nr, nc})
}
}
}

// atlantic에서 시작하는 BFS
for c := 0; c < n; c++ {
queue = append(queue, []int{m - 1, c})
}
for r := 0; r < m-1; r++ {
queue = append(queue, []int{r, n - 1})
}

for len(queue) > 0 {
r, c := queue[0][0], queue[0][1]
queue = queue[1:]

memo[r][c].atlantic = true

for i := 0; i < 4; i++ {
nr, nc := r+dr[i], c+dc[i]

if !(0 <= nr && nr < m && 0 <= nc && nc < n) {
continue
}

if heights[r][c] <= heights[nr][nc] && !memo[nr][nc].atlantic {
memo[nr][nc].atlantic = true
queue = append(queue, []int{nr, nc})
}
}
}

for r, row := range memo {
for c, el := range row {
if el.pacific && el.atlantic {
res = append(res, []int{r, c})
}
}
}

return res
}