diff --git a/alien-dictionary/flynn.go b/alien-dictionary/flynn.go new file mode 100644 index 000000000..ca6d4fd1d --- /dev/null +++ b/alien-dictionary/flynn.go @@ -0,0 +1,105 @@ +/* +풀이 +- 두 단어를 알파벳 하나씩 차례대로 비교했을 때, 첫번째 차이가 발생하는 지점에서 alien dictionary의 order를 찾을 수 있습니다 +- 첫번째 단어부터 바로 그 다음 단어와 두 단어씩 짝지어서 비교하면 앞에서 말한 일련의 order를 찾아낼 수 있습니다 + 알파벳 x가 알파벳 y보다 alien dictionary order에서 앞서는 관계, 즉 x->y인 관계를 찾아서 x: {y1, y2, y3, ...}인 집합의 map을 만들겠습니다 + 그리고 이를 nextLetters라고 명명하였습니다 +- 만약 특정 알파벳 x에 대해, z->x인 알파벳 z가 없다면 x는 우리가 정답으로 제출할 result string의 어느 위치에든 자유롭게 끼워넣을 수 있습니다 + (* If there are multiple solutions, return any of them.) + 우리는 이런 알파벳 x를 찾아낼 때마다 바로바로 result string res에 추가하겠습니다 +- z->x인 알파벳 z가 현재 있는지 없는지에 대한 상태관리를 하기 위해서 prevLetterCounts라는 map을 만들겠습니다 + prevLetterCounts[x]: z->x인 z의 개수 +- nextLetters, prevLetterCounts를 잘 생성한 후에는 prevLetterCount가 0인 알파벳부터 queue에 등록시킨 후 BFS를 실행합니다 + BFS를 실행하며 prevLetterCount가 0인 알파벳이 새로 발견될 경우 queue에 등록시킵니다 +- 엣지케이스가 두 경우 발생하는데, + 첫번째는 nextLetters를 생성하는 반복문에서 발견됩니다 + 두번째 단어가 첫번째 단어의 prefix인 경우는 애초부터 잘못된 dictionary order인 경우입니다 + 위 경우는 단순 알파벳 비교로는 발견하기 어려우므로 flag를 사용하였습니다 + 두번째는 result string의 길이가 input으로 주어진 단어들에 등장한 알파벳의 개수보다 적은 경우입니다 + 이 경우는 nextLetters에 순환이 발생한 경우이므로 dictionary order가 잘못되었다고 판단할 수 있습니다 +Big O +- N: 주어진 배열 words의 길이 +- S(W): 배열 words에 속한 모든 string의 길이의 총합 +- Time complexity: O(N + S(W)) + - prevLetterCounts와 nextLetters 생성 -> O(N) + - nextLetters에 들어갈 알파벳 전후관계 찾기 -> O(S(W)) + - 알파벳 소문자의 수는 제한되어 있기 때문에 BFS의 시간 복잡도 상한선은 정해져 있습니다 -> O(26 * 26) = O(1) +- Space complexity: O(1) + - 알파벳 소문자의 수는 제한되어 있기 때문에 공간 복잡도의 상한선은 정해져 있습니다 + prevLetterCounts -> O(26) = O(1) + nextLetters -> O(26 * 26) = O(1) + queue -> O(26) = O(1) +*/ + +import "strings" + +func alienOrder(words []string) string { + n := len(words) + // prevLetterCounts[x] = count of letters y that are in relation of y->x + prevLetterCounts := make(map[string]int) + // nextLetters[x] = set of letters y that are in relation of x->y + nextLetters := make(map[string]map[string]bool) + for _, word := range words { + for _, c := range word { + if _, ok := prevLetterCounts[string(c)]; !ok { + prevLetterCounts[string(c)] = 0 + nextLetters[string(c)] = make(map[string]bool) + } + } + } + + for i := 0; i < n-1; i++ { + currWord := words[i] + nextWord := words[i+1] + // flag for edge case below + diff := false + for j := 0; j < len(currWord) && j < len(nextWord); j++ { + if currWord[j] != nextWord[j] { + diff = true + if _, ok := nextLetters[string(currWord[j])][string(nextWord[j])]; !ok { + prevLetterCounts[string(nextWord[j])]++ + nextLetters[string(currWord[j])][string(nextWord[j])] = true + } + break + } + } + // tricky edge case!!! + // if nextWord is prefix of currWord, then the provided dictionary order is invalid + if !diff && len(currWord) > len(nextWord) { + return "" + } + } + // BFS + queue := make([]string, 0, len(prevLetterCounts)) + for letter := range prevLetterCounts { + // we can arrange letters whose prevLetterCount is zero as we wish + if prevLetterCounts[letter] == 0 { + queue = append(queue, letter) + } + } + // in Go, using strings.Builder is the most efficient way to build strings + var sb strings.Builder + for len(queue) > 0 { + // pop the letter from the queue and append it to the result string + popped := queue[0] + queue = queue[1:] + sb.WriteString(popped) + + for nextLetter := range nextLetters[popped] { + prevLetterCounts[nextLetter]-- + // if prevLetterCount for nextLetter becomes zero, we can add it to the queue + // append to the result string (order) in the next iteration + if prevLetterCounts[nextLetter] == 0 { + queue = append(queue, nextLetter) + } + } + } + // res is result string + res := sb.String() + // this case means that there was a cycle + if len(res) != len(prevLetterCounts) { + return "" + } + // else return res + return res +} diff --git a/longest-palindromic-substring/flynn.go b/longest-palindromic-substring/flynn.go new file mode 100644 index 000000000..528929dc9 --- /dev/null +++ b/longest-palindromic-substring/flynn.go @@ -0,0 +1,54 @@ +/* +풀이 +- 슬라이딩 윈도우 기법을 이용하면 풀이할 수 있습니다 +Big O +- N: 주어진 문자열 s의 길이 +- Time complexity: O(N^2) + - window 함수가 O(N)의 시간복잡도를 가지므로 + 각 반복문은 O(N * N) = O(N^2)의 시간복잡도를 가진다고 볼 수 있습니다 +- Space complexity: O(1) + - 별도의 추가적인 공간 복잡도를 고려하지 않아도 됩니다 +*/ + +func longestPalindrome(s string) string { + n := len(s) + maxStart := 0 + maxEnd := 0 + // for odd lengths + for i := 0; i < n; i++ { + window(&s, &maxStart, &maxEnd, i, false) + } + // for even lengths + for i := 0; i < n-1; i++ { + window(&s, &maxStart, &maxEnd, i, true) + } + + return s[maxStart : maxEnd+1] +} + +/* +helper function for searching palindromic substring +from the pivotal index `i` +*/ +func window(s *string, maxStart *int, maxEnd *int, i int, isEven bool) { + n := len(*s) + start := i + end := i + if isEven { + end++ + } + for 0 <= start && end < n { + if (*s)[start] != (*s)[end] { + break + } + + // if new palindromic substring is longer than the previously found one, + // update the start and end index + if *maxEnd-*maxStart < end-start { + *maxStart = start + *maxEnd = end + } + start-- + end++ + } +} diff --git a/rotate-image/flynn.go b/rotate-image/flynn.go new file mode 100644 index 000000000..ff02e04a1 --- /dev/null +++ b/rotate-image/flynn.go @@ -0,0 +1,39 @@ +/* +풀이 +- matrix를 4사분면으로 나눕니다 + 1사분면의 모든 좌표에 대해 아래와 같은 연산을 수행합니다 +- 1사분면의 좌표 a1에 대해 a2, a3, a4를 아래처럼 정의합니다 + a2: a1을 90도 회전시켰을 때의 좌표 (2사분면에 위치함) + a3: a2를 90도 회전시켰을 때의 좌표 (3사분면에 위치함) + a4: a3을 90도 회전시켰을 때의 좌표 (4사분면에 위치함) + a1 -> a2, a2 -> a3, a3 -> a4, a4 -> a1으로 값을 변경시킵니다 +Big O +- N: 매트릭스의 크기 +- Time complexity: O(N^2) +- Space complexity: O(1) +*/ + +func rotate(matrix [][]int) { + n := len(matrix) + // 사분면의 크기, qr, qc: 사분면의 행, 열 크기 + qr := n / 2 + qc := (n + 1) / 2 + + for r := 0; r < qr; r++ { + for c := 0; c < qc; c++ { + r1 := r + c1 := c + + r2 := c + c2 := n - 1 - r + + r3 := n - 1 - r + c3 := n - 1 - c + + r4 := n - 1 - c + c4 := r + + matrix[r1][c1], matrix[r2][c2], matrix[r3][c3], matrix[r4][c4] = matrix[r4][c4], matrix[r1][c1], matrix[r2][c2], matrix[r3][c3] + } + } +} diff --git a/subtree-of-another-tree/flynn.go b/subtree-of-another-tree/flynn.go new file mode 100644 index 000000000..04a35e441 --- /dev/null +++ b/subtree-of-another-tree/flynn.go @@ -0,0 +1,50 @@ +/* +풀이 +- 두 트리가 동일한지 검사하는 dfs 함수를 이용하여 풀이할 수 있습니다 +Big O +- M: root 트리의 노드 개수 +- N: subRoot 트리의 노드 개수 +- Time complexity: O(MN) + - 최악의 경우 root 트리의 모든 노드에 대해 isSameTree 함수를 실행 (O(M)) + 최악의 경우 isSameTree 함수는 O(N)의 시간복잡도를 가짐 +- Space complexity: O(M+N) + - isSubTree의 재귀호출스택 깊이는 최대 O(M)의 공간복잡도를 가짐 + - isSameTree의 재귀호출스택 깊이는 최대 O(N)의 공간복잡도를 가짐 +*/ + +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ + func isSubtree(root *TreeNode, subRoot *TreeNode) bool { + // handling nil(null) inputs + if root == nil { + return false + } + // return true if root and subroot are same + if isSameTree(root, subRoot) { + return true + } + // else, check root.left and root.right + return isSubtree(root.Left, subRoot) || isSubtree(root.Right, subRoot) +} + +/* +dfs helper function checking whether two treenodes are same or not +*/ +func isSameTree(a *TreeNode, b *TreeNode) bool { + // handling nil(null) cases + if a == nil || b == nil { + return a == b + } + // falsy cases + if a.Val != b.Val || !isSameTree(a.Left, b.Left) || !isSameTree(a.Right, b.Right) { + return false + } + // else, return true + return true +} diff --git a/validate-binary-search-tree/flynn.go b/validate-binary-search-tree/flynn.go new file mode 100644 index 000000000..7c9496cdf --- /dev/null +++ b/validate-binary-search-tree/flynn.go @@ -0,0 +1,46 @@ +/* +풀이 +- BST의 속성을 이해한 후, 해당 속성을 검사하는 dfs 함수를 이용하면 풀이할 수 있습니다 +Big O +- N: root 트리의 노드 개수 +- Time complexity: O(N) + - 모든 노드에 대해 최대 1번의 탐색이 필요합니다 +- Space complexity: O(logN) (O(N) at worst) + - check 함수의 재귀호출 스택 깊이는 트리의 높이에 비례하여 증가하므로 일반적으로 O(logN)의 공간복잡도를 가진다고 볼 수 있습니다 + 하지만 트리가 심하게 치우친 경우 O(N)까지 커질 수 있습니다 +*/ + +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ + + const ( + MIN = -(2_147_483_648 + 1) + MAX = 2_147_483_647 + 1 +) + +func isValidBST(root *TreeNode) bool { + return check(root.Left, MIN, root.Val) && check(root.Right, root.Val, MAX) +} + +/* +helper dfs function +*/ + +func check(node *TreeNode, min int, max int) bool { + // base case + if node == nil { + return true + } + // node.val should be in the boundary (min, max) + if !(min < node.Val && node.Val < max) { + return false + } + // check for children nodes + return check(node.Left, min, node.Val) && check(node.Right, node.Val, max) +}