Skip to content

Commit 55e4bc8

Browse files
authoredOct 11, 2024
Merge pull request #520 from obzva/main
[Flynn] Week 09
2 parents bfe247c + 85d05c1 commit 55e4bc8

File tree

5 files changed

+331
-0
lines changed

5 files changed

+331
-0
lines changed
 
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
풀이 1
3+
- 주어진 배열을 두 부분으로 나눌 수 있기 때문에 이진탐색을 이용하여 풀이할 수 있습니다
4+
주어진 배열이 A = [4,5,6,7,0,1,2] 라고 할 때, 이 배열은 두 부분으로 나눌 수 있습니다
5+
A[0:3] : rotate되어 앞으로 배열의 앞으로 옮겨진 부분
6+
A[4:6] : rotate되어 배열의 뒤로 밀려난 부분
7+
이걸 조금 다르게 표현하면 이렇게도 바라볼 수 있습니다
8+
f(A) = [T, T, T, T, F, F, F] (T/F: rotate되어 배열의 앞으로 옮겨진 부분인가?)
9+
10+
이 때, 우리가 찾는 값 (the Minimum in the rotated sorted array)는 두 구간의 경계에 위치하고 있습니다
11+
즉, 첫번째 F의 위치를 찾는 문제로 바꿔 생각할 수 있단 뜻입니다
12+
13+
Big O
14+
- N: 주어진 배열 nums의 길이
15+
16+
- Time complexity: O(logN)
17+
- Space complexity: O(1)
18+
*/
19+
20+
func findMin(nums []int) int {
21+
lo, hi := 0, len(nums)-1
22+
// rotate가 0번 실행된 경우, 이진탐색을 실행할 필요가 없고 이진탐색의 초기값 설정이 까다로워지기 때문에 처음에 따로 처리해줍니다
23+
// 앞서 hi의 초기값을 설정할 때, len(nums)가 아닌 len(nums) - 1로 설정할 수 있었던 이유이기도 합니다
24+
if nums[lo] < nums[hi] {
25+
return nums[lo]
26+
}
27+
28+
// Go는 while문에 대응하는 표현을 for로 이렇게 표현합니다
29+
for lo < hi {
30+
mid := lo + (hi-lo)/2
31+
32+
// 만일 mid가 앞으로 밀려난 부분에 속한다면...
33+
if nums[lo] <= nums[mid] && nums[mid] > nums[hi] {
34+
lo = mid + 1
35+
} else {
36+
hi = mid
37+
}
38+
}
39+
40+
return nums[lo]
41+
}
42+
43+
/*
44+
풀이 2
45+
- 풀이 1에서 덜어낼 수 있는 로직을 덜어낸 좀 더 깔끔한 이진탐색 풀이입니다
46+
47+
Big O
48+
- 풀이 1과 동일
49+
*/
50+
51+
func findMin(nums []int) int {
52+
lo, hi := 0, len(nums)
53+
54+
for lo < hi {
55+
mid := lo+(hi-lo)/2
56+
57+
if nums[mid] > nums[len(nums)-1] {
58+
lo = mid + 1
59+
} else {
60+
hi = mid
61+
}
62+
}
63+
64+
return nums[lo]
65+
}

‎linked-list-cycle/flynn.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* 풀이
3+
* - Floyd's Tortoise and Hare Algorithm을 이용한 풀이입니다.
4+
* 참고: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare
5+
*
6+
* Big O
7+
* - N: 노드의 개수
8+
* - L: 루프 구간에 속하는 노드의 개수
9+
*
10+
* Time Complexity: O(N)
11+
* - 루프가 없는 경우:
12+
* - fast 포인터가 링크드리스트의 끝까지 이동하면 종료합니다.
13+
* - 이 때 fast 포인터의 탐색 시간 복잡도는 다음과 같습니다:
14+
* O(N / 2) = O(N)
15+
* - 루프가 있는 경우:
16+
* - slow 포인터와 fast 포인터가 루프 안에서 만나면 종료합니다.
17+
* - 이 때 slow 포인터의 탐색 시간 복잡도는 다음과 같습니다:
18+
* O((N - L) + L * c) (c는 slow가 fast를 만날 때까지 루프를 반복한 횟수)
19+
* = O(r + (N - r) * c) (L은 0 <= r <= N인 r에 대해 N - r로 표현할 수 있습니다)
20+
* = O(N)
21+
*
22+
* Space Complexity: O(1)
23+
* - 노드의 개수에 상관없이 일정한 공간을 사용합니다.
24+
*/
25+
26+
/**
27+
* Definition for singly-linked list.
28+
* type ListNode struct {
29+
* Val int
30+
* Next *ListNode
31+
* }
32+
*/
33+
func hasCycle(head *ListNode) bool {
34+
if head == nil {
35+
return false
36+
}
37+
38+
slow := head
39+
fast := head
40+
41+
for fast != nil && fast.Next != nil {
42+
slow = slow.Next
43+
fast = fast.Next.Next
44+
45+
if slow == fast {
46+
return true
47+
}
48+
}
49+
50+
return false
51+
}

‎maximum-subarray/flynn.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
풀이 1
3+
- 아래와 같은 memo 배열을 만들어서 풀이할 수 있습니다 (참고: Kadane's Algorithm)
4+
memo[i] = nums[:i] 중에서 nums[i]를 반드시 포함하는 부분 배열의 최대 합
5+
6+
Big O
7+
- N: 주어진 배열 nums의 길이
8+
- Time complexity: O(N)
9+
- Space complexity: O(N)
10+
*/
11+
12+
func maxSubArray(nums []int) int {
13+
n := len(nums)
14+
15+
memo := make([]int, n)
16+
copy(memo, nums)
17+
18+
maxSum := nums[0]
19+
20+
for i := 1; i < n; i++ {
21+
if memo[i-1] > 0 {
22+
memo[i] += memo[i-1]
23+
}
24+
25+
if maxSum < memo[i] {
26+
maxSum = memo[i]
27+
}
28+
}
29+
30+
return maxSum
31+
}
32+
33+
/*
34+
풀이 2
35+
- 알고리즘의 전개 과정을 보면 O(N)의 공간복잡도를 갖는 memo가 필요하지 않다는 걸 알 수 있습니다
36+
- memo 배열 대신 현재 계산 중인 부분 배열의 합만 계속 갱신합니다
37+
38+
Big O
39+
- N: 주어진 배열 nums의 길이
40+
- Time complexity: O(N)
41+
- Space complexity: O(1)
42+
*/
43+
44+
func maxSubArray(nums []int) int {
45+
maxSum, currSum := nums[0], nums[0]
46+
47+
for i := 1; i < len(nums); i++ {
48+
if currSum > 0 {
49+
currSum += nums[i]
50+
} else {
51+
currSum = nums[i]
52+
}
53+
54+
if maxSum < currSum {
55+
maxSum = currSum
56+
}
57+
}
58+
59+
return maxSum
60+
}

‎minimum-window-substring/flynn.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
풀이
3+
- 슬라이딩 윈도우를 이용하여 풀이합니다
4+
- 주어진 문자열 t의 각 문자와 그 개수를 tMap이라는 해시맵에 저장합니다
5+
- 현재 슬라이딩 윈도우에 포함된 문자와 그 개수를 sMap이라는 해시맵에 저장합니다
6+
- 슬라이딩 윈도우의 끝단(end)을 넓혀나가면서 sMap과 tMap을 비교합니다
7+
이 때 슬라이딩 윈도우가 t의 모든 문자를 포함하는 경우를 찾으면 시작단(start)을 좁혀주면서 슬라이딩 윈도우의 최소폭을 찾습니다
8+
슬라이딩 윈도우가 t의 모든 문자를 포함하는지를 판별하기 위해서 match라는 변수를 사용합니다
9+
match는 sMap[c] == tMap[c]인 문자 c의 개수이며, match == len(tMap) 즉 match가 t의 고유 문자 개수와 같다면 슬라이딩 윈도우가 t의 모든 문자를 포함하는 것이라고 볼 수 있습니다
10+
슬라이딩 윈도우를 좁혀줄 때에도 match의 감소 여부를 잘 관찰하며 최소폭을 갱신합니다
11+
슬라이딩 윈도우를 줄일만큼 줄였다면 다시 슬라이딩 윈도우의 끝단을 넓혀나가면서 다른 경우의 수를 찾습니다
12+
13+
Big O
14+
- M: 주어진 문자열 s의 길이
15+
- N: 주어진 문자열 t의 길이
16+
- Time complexity: O(M + N)
17+
- 문자열 s를 순회하는 반복문 내에서 각 문자는 최대 두 번 조회될 수 있습니다 (start, end) -> O(2M)
18+
- 문자열 t로 해시맵을 만드는 데에는 O(N)의 시간복잡도가 소요됩니다
19+
- O(2M + N) -> O(M + N)
20+
- Space complexity: O(M + N)
21+
- 두 개의 해시맵을 사용하므로 O(M + N)의 공간복잡도를 갖습니다
22+
*/
23+
24+
func minWindow(s string, t string) string {
25+
sMap, tMap := map[string]int{}, map[string]int{}
26+
for _, r := range t {
27+
tMap[string(r)]++
28+
}
29+
30+
start, end, matched, res := 0, 0, 0, ""
31+
for end < len(s) {
32+
sMap[string(s[end])]++
33+
34+
if sMap[string(s[end])] == tMap[string(s[end])] {
35+
matched++
36+
}
37+
38+
for matched == len(tMap) {
39+
if res == "" || len(res) > end-start+1 {
40+
res = s[start : end+1]
41+
}
42+
43+
if sMap[string(s[start])] == tMap[string(s[start])] {
44+
matched--
45+
}
46+
sMap[string(s[start])]--
47+
start++
48+
}
49+
50+
end++
51+
}
52+
53+
return res
54+
}

‎pacific-atlantic-water-flow/flynn.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
풀이
3+
- BFS를 이용하여 풀이할 수 있습니다
4+
- 주어진 배열의 가장자리에서부터 시작하는 BFS를 pacific과 atlantic에 대해 각각 실행합니다
5+
6+
Big O
7+
- M: 주어진 배열의 행의 개수
8+
- N: 주어진 배열의 열의 개수
9+
- Time complexity: O(MN)
10+
- 탐색을 진행하는 회수는 배열의 원소 개수에 비례하여 증가합니다
11+
- Space complexity: O(MN)
12+
- queue의 최대 크기는 배열의 원소 개수에 비례하여 증가합니다
13+
- memo 배열의 크기는 배열의 원소 개수에 비례하여 증가합니다
14+
*/
15+
16+
type pair struct {
17+
pacific bool
18+
atlantic bool
19+
}
20+
21+
func pacificAtlantic(heights [][]int) [][]int {
22+
var res [][]int
23+
24+
m, n := len(heights), len(heights[0])
25+
26+
dr := []int{0, 0, 1, -1}
27+
dc := []int{1, -1, 0, 0}
28+
29+
// 모든 r, c에 대해 memo[r][c] = {pacific: false, atlantic: false}로 초기화합니다
30+
memo := make([][]pair, m)
31+
for r := range memo {
32+
memo[r] = make([]pair, n)
33+
}
34+
35+
queue := make([][]int, 0)
36+
37+
// pacific에서 시작하는 BFS
38+
for c := 0; c < n; c++ {
39+
queue = append(queue, []int{0, c})
40+
}
41+
for r := 1; r < m; r++ {
42+
queue = append(queue, []int{r, 0})
43+
}
44+
45+
for len(queue) > 0 {
46+
r, c := queue[0][0], queue[0][1]
47+
queue = queue[1:]
48+
49+
memo[r][c].pacific = true
50+
51+
for i := 0; i < 4; i++ {
52+
nr, nc := r+dr[i], c+dc[i]
53+
54+
if !(0 <= nr && nr < m && 0 <= nc && nc < n) {
55+
continue
56+
}
57+
58+
if heights[r][c] <= heights[nr][nc] && !memo[nr][nc].pacific {
59+
queue = append(queue, []int{nr, nc})
60+
}
61+
}
62+
}
63+
64+
// atlantic에서 시작하는 BFS
65+
for c := 0; c < n; c++ {
66+
queue = append(queue, []int{m - 1, c})
67+
}
68+
for r := 0; r < m-1; r++ {
69+
queue = append(queue, []int{r, n - 1})
70+
}
71+
72+
for len(queue) > 0 {
73+
r, c := queue[0][0], queue[0][1]
74+
queue = queue[1:]
75+
76+
memo[r][c].atlantic = true
77+
78+
for i := 0; i < 4; i++ {
79+
nr, nc := r+dr[i], c+dc[i]
80+
81+
if !(0 <= nr && nr < m && 0 <= nc && nc < n) {
82+
continue
83+
}
84+
85+
if heights[r][c] <= heights[nr][nc] && !memo[nr][nc].atlantic {
86+
memo[nr][nc].atlantic = true
87+
queue = append(queue, []int{nr, nc})
88+
}
89+
}
90+
}
91+
92+
for r, row := range memo {
93+
for c, el := range row {
94+
if el.pacific && el.atlantic {
95+
res = append(res, []int{r, c})
96+
}
97+
}
98+
}
99+
100+
return res
101+
}

0 commit comments

Comments
 (0)
Please sign in to comment.