From eac930dda2e4e0ed4ba6754444afecd05ced064c Mon Sep 17 00:00:00 2001 From: obzva Date: Mon, 7 Oct 2024 09:16:38 +0900 Subject: [PATCH 1/7] Solution Linked List Cycle --- linked-list-cycle/flynn.go | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 linked-list-cycle/flynn.go diff --git a/linked-list-cycle/flynn.go b/linked-list-cycle/flynn.go new file mode 100644 index 000000000..b441f6d4c --- /dev/null +++ b/linked-list-cycle/flynn.go @@ -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 +} From eade9cdeae3159ccf75e40ef24558e740267f77d Mon Sep 17 00:00:00 2001 From: obzva Date: Fri, 11 Oct 2024 03:41:26 +0900 Subject: [PATCH 2/7] Solution: Find Minimum in Rotated Sorted Array --- find-minimum-in-rotated-sorted-array/flynn.go | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 find-minimum-in-rotated-sorted-array/flynn.go diff --git a/find-minimum-in-rotated-sorted-array/flynn.go b/find-minimum-in-rotated-sorted-array/flynn.go new file mode 100644 index 000000000..92a491060 --- /dev/null +++ b/find-minimum-in-rotated-sorted-array/flynn.go @@ -0,0 +1,40 @@ +/* +풀이 +- 주어진 배열을 두 부분으로 나눌 수 있기 때문에 이진탐색을 이용하여 풀이할 수 있습니다 + 주어진 배열이 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] + } + + 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] +} From 61b1e5d02ea18ac34c16a19a66a18cfc430ed8dc Mon Sep 17 00:00:00 2001 From: obzva Date: Fri, 11 Oct 2024 03:42:50 +0900 Subject: [PATCH 3/7] add comment --- find-minimum-in-rotated-sorted-array/flynn.go | 1 + 1 file changed, 1 insertion(+) diff --git a/find-minimum-in-rotated-sorted-array/flynn.go b/find-minimum-in-rotated-sorted-array/flynn.go index 92a491060..c80ae8470 100644 --- a/find-minimum-in-rotated-sorted-array/flynn.go +++ b/find-minimum-in-rotated-sorted-array/flynn.go @@ -25,6 +25,7 @@ func findMin(nums []int) int { return nums[lo] } + // Go는 while문에 대응하는 표현을 for로 이렇게 표현합니다 for lo < hi { mid := lo + (hi-lo)/2 From 50d78a0cd7c1cf144a7e4bc3aa3720c254e9e9f6 Mon Sep 17 00:00:00 2001 From: obzva Date: Fri, 11 Oct 2024 05:04:52 +0900 Subject: [PATCH 4/7] Solution: Pacific Atlantic Water Flow --- pacific-atlantic-water-flow/flynn.go | 101 +++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 pacific-atlantic-water-flow/flynn.go diff --git a/pacific-atlantic-water-flow/flynn.go b/pacific-atlantic-water-flow/flynn.go new file mode 100644 index 000000000..59d69b729 --- /dev/null +++ b/pacific-atlantic-water-flow/flynn.go @@ -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 +} From d30465597f0a16fc12560e71a8631673bf7d6782 Mon Sep 17 00:00:00 2001 From: obzva Date: Fri, 11 Oct 2024 10:06:22 +0900 Subject: [PATCH 5/7] Solution: Maximum Subarray --- maximum-subarray/flynn.go | 60 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 maximum-subarray/flynn.go diff --git a/maximum-subarray/flynn.go b/maximum-subarray/flynn.go new file mode 100644 index 000000000..e1748cb38 --- /dev/null +++ b/maximum-subarray/flynn.go @@ -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 +} From eb0c7cab6dd4c3338011e6a69b20d6f97de9d41e Mon Sep 17 00:00:00 2001 From: obzva Date: Fri, 11 Oct 2024 11:19:25 +0900 Subject: [PATCH 6/7] Solution: Minimum Window Substring --- minimum-window-substring/flynn.go | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 minimum-window-substring/flynn.go diff --git a/minimum-window-substring/flynn.go b/minimum-window-substring/flynn.go new file mode 100644 index 000000000..940b78ae6 --- /dev/null +++ b/minimum-window-substring/flynn.go @@ -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 +} From 85d05c1861e0e030f932b71833160dbfa7cae981 Mon Sep 17 00:00:00 2001 From: obzva Date: Fri, 11 Oct 2024 11:42:58 +0900 Subject: [PATCH 7/7] add cleaner solution --- find-minimum-in-rotated-sorted-array/flynn.go | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/find-minimum-in-rotated-sorted-array/flynn.go b/find-minimum-in-rotated-sorted-array/flynn.go index c80ae8470..32c9a383c 100644 --- a/find-minimum-in-rotated-sorted-array/flynn.go +++ b/find-minimum-in-rotated-sorted-array/flynn.go @@ -1,5 +1,5 @@ /* -풀이 +풀이 1 - 주어진 배열을 두 부분으로 나눌 수 있기 때문에 이진탐색을 이용하여 풀이할 수 있습니다 주어진 배열이 A = [4,5,6,7,0,1,2] 라고 할 때, 이 배열은 두 부분으로 나눌 수 있습니다 A[0:3] : rotate되어 앞으로 배열의 앞으로 옮겨진 부분 @@ -39,3 +39,27 @@ func findMin(nums []int) int { 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] +}