Skip to content

Commit 9ee119f

Browse files
authored
Merge branch 'DaleStudy:main' into main
2 parents a9d406d + 72e93b7 commit 9ee119f

File tree

19 files changed

+1019
-0
lines changed

19 files changed

+1019
-0
lines changed

alien-dictionary/EGON.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from collections import deque
2+
from typing import List
3+
from unittest import TestCase, main
4+
5+
6+
class Solution:
7+
def foreignDictionary(self, words: List[str]) -> str:
8+
return self.solve_topological_sort(words)
9+
10+
"""
11+
LintCode 로그인이 안되어서 https://neetcode.io/problems/foreign-dictionary 에서 실행시키고 통과만 확인했습니다.
12+
13+
Runtime: ? ms (Beats ?%)
14+
Time Complexity:
15+
#0. 복잡도 변수 정의
16+
- words 배열의 길이를 n
17+
- words 배열을 이루는 단어들의 평균 길이를 l
18+
- words 배열을 이루는 단어를 이루는 문자들의 총 갯수를 c (= n * l)
19+
- words 배열을 이루는 단어를 이루는 문자들의 중복 제거 집합의 크기를 s라 하자
20+
21+
#1. 초기화
22+
- words 배열을 이루는 단어를 이루는 문자들을 조회하며 char_set을 초기화하는데 O(c)
23+
- 위상정렬에 사용할 graph 딕셔너리 초기화를 위해 char_set의 크기만큼 조회하므로 O(s)
24+
- 마찬가지로 위상정렬에 사용할 rank 딕셔너리 초기화에 O(s)
25+
> O(c) + O(s) + O(s) ~= O(c + s)
26+
27+
#2. 위상정렬 간선 초기화
28+
- words 배열을 조회하는데 O(n - 1)
29+
- 단어 간 접두사 관계인 경우, 체크하는 startswith 메서드 사용에 * O(l)
30+
- 단어 간 접두사 관계가 아닌 경우, first_char, second_char를 구하는데
31+
- zip 생성에 O(l)
32+
- zip 조회에 * O(l)
33+
> O(n - 1) * (O(l) + O(l) * O(l)) ~= O(n) * O(l ^ 2) ~= O(c * l) ~= O(c)
34+
35+
#3. 위상정렬 실행
36+
- dq 초기화에 rank 딕셔너리의 모든 키를 조회하는데 O(s)
37+
- dq를 이용해서 graph 딕셔너리의 모든 values를 순회하는데, #2에서 각 first_char, second_char마다 1회 value가 추가되었으므로, 중복이 없는 경우 최대 O(n), upper bound
38+
> O(s) + O(n) ~= O(s + n), upper bound
39+
40+
#4. 최종 계산
41+
> O(c + s) + O(c) + O(s + n) ~= O(c + s) + O(s + n) = O(n * l + s) + O(n + s) ~= O(n * l + s), upper bound
42+
43+
Memory: ? MB (Beats ?%)
44+
Space Complexity: O(s + c)
45+
- char_set의 크기에서 O(s)
46+
- graph의 keys는 최대 s개이고 values는 최대 c개이므로 O(s + c), upper bound
47+
- rank의 keys의 크기에서 O(s)
48+
- dq의 최대 크기는 rank의 크기와 같으므로 O(s)
49+
> O(s) + O(s + c) + O(s) + O(s) ~= O(s + c)
50+
"""
51+
def solve_topological_sort(self, words: List[str]) -> str:
52+
if not words:
53+
return ""
54+
55+
char_set = set([char for word in words for char in word])
56+
graph = {char: set() for char in char_set}
57+
rank = {char: 0 for char in char_set}
58+
for i in range(len(words) - 1):
59+
first_word, second_word = words[i], words[i + 1]
60+
61+
if len(first_word) > len(second_word) and first_word.startswith(second_word):
62+
return ""
63+
64+
first_char, second_char = next(((fc, sc) for fc, sc in zip(first_word, second_word) if fc != sc), ("", ""))
65+
if (first_char and second_char) and second_char not in graph[first_char]:
66+
graph[first_char].add(second_char)
67+
rank[second_char] += 1
68+
69+
result = []
70+
dq = deque([char for char in rank if rank[char] == 0])
71+
while dq:
72+
curr_char = dq.popleft()
73+
result.append(curr_char)
74+
for post_char in graph[curr_char]:
75+
rank[post_char] -= 1
76+
if rank[post_char] == 0:
77+
dq.append(post_char)
78+
79+
if len(result) != len(rank):
80+
return ""
81+
else:
82+
return "".join(result)
83+
84+
85+
class _LeetCodeTestCases(TestCase):
86+
def test_1(self):
87+
words = ["z","o"]
88+
output = "zo"
89+
solution = Solution()
90+
self.assertEqual(solution.foreignDictionary(words), output)
91+
92+
def test_2(self):
93+
words = ["hrn","hrf","er","enn","rfnn"]
94+
output = "hernf"
95+
solution = Solution()
96+
self.assertEqual(solution.foreignDictionary(words), output)
97+
98+
def test_3(self):
99+
words = ["wrt","wrf","er","ett","rftt","te"]
100+
output = "wertf"
101+
solution = Solution()
102+
self.assertEqual(solution.foreignDictionary(words), output)
103+
104+
105+
if __name__ == '__main__':
106+
main()

alien-dictionary/HC-kang.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* https://leetcode.com/problems/alien-dictionary
3+
* T.C. O(N * M) N: number of words, M: average length of word
4+
* S.C. O(1) 26 characters
5+
*/
6+
function alienOrder(words: string[]): string {
7+
const graph: Record<string, Set<string>> = {};
8+
const inDegree: Record<string, number> = {};
9+
10+
// Initialize the graph
11+
for (const word of words) {
12+
for (const char of word) {
13+
if (!graph[char]) {
14+
graph[char] = new Set();
15+
inDegree[char] = 0;
16+
}
17+
}
18+
}
19+
20+
// Build the graph
21+
for (let i = 0; i < words.length - 1; i++) {
22+
const word1 = words[i];
23+
const word2 = words[i + 1];
24+
25+
// Check invalid case: if word1 is longer and is prefix of word2
26+
if (word1.length > word2.length && word1.startsWith(word2)) {
27+
return '';
28+
}
29+
30+
let j = 0;
31+
while (j < Math.min(word1.length, word2.length)) {
32+
if (word1[j] !== word2[j]) {
33+
const curSet = graph[word1[j]];
34+
if (!curSet.has(word2[j])) {
35+
curSet.add(word2[j]);
36+
inDegree[word2[j]]++;
37+
}
38+
break;
39+
}
40+
j++;
41+
}
42+
}
43+
44+
// Topological sort
45+
const queue: string[] = [];
46+
for (const [char, degree] of Object.entries(inDegree)) {
47+
if (degree === 0) {
48+
queue.push(char);
49+
}
50+
}
51+
52+
const result: string[] = [];
53+
while (queue.length) {
54+
const char = queue.shift();
55+
result.push(char!);
56+
for (const next of graph[char!]) {
57+
inDegree[next]--;
58+
if (inDegree[next] === 0) {
59+
queue.push(next);
60+
}
61+
}
62+
}
63+
64+
return result.length === Object.keys(inDegree).length //
65+
? result.join('')
66+
: '';
67+
}

alien-dictionary/flynn.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
풀이
3+
- 두 단어를 알파벳 하나씩 차례대로 비교했을 때, 첫번째 차이가 발생하는 지점에서 alien dictionary의 order를 찾을 수 있습니다
4+
- 첫번째 단어부터 바로 그 다음 단어와 두 단어씩 짝지어서 비교하면 앞에서 말한 일련의 order를 찾아낼 수 있습니다
5+
알파벳 x가 알파벳 y보다 alien dictionary order에서 앞서는 관계, 즉 x->y인 관계를 찾아서 x: {y1, y2, y3, ...}인 집합의 map을 만들겠습니다
6+
그리고 이를 nextLetters라고 명명하였습니다
7+
- 만약 특정 알파벳 x에 대해, z->x인 알파벳 z가 없다면 x는 우리가 정답으로 제출할 result string의 어느 위치에든 자유롭게 끼워넣을 수 있습니다
8+
(* If there are multiple solutions, return any of them.)
9+
우리는 이런 알파벳 x를 찾아낼 때마다 바로바로 result string res에 추가하겠습니다
10+
- z->x인 알파벳 z가 현재 있는지 없는지에 대한 상태관리를 하기 위해서 prevLetterCounts라는 map을 만들겠습니다
11+
prevLetterCounts[x]: z->x인 z의 개수
12+
- nextLetters, prevLetterCounts를 잘 생성한 후에는 prevLetterCount가 0인 알파벳부터 queue에 등록시킨 후 BFS를 실행합니다
13+
BFS를 실행하며 prevLetterCount가 0인 알파벳이 새로 발견될 경우 queue에 등록시킵니다
14+
- 엣지케이스가 두 경우 발생하는데,
15+
첫번째는 nextLetters를 생성하는 반복문에서 발견됩니다
16+
두번째 단어가 첫번째 단어의 prefix인 경우는 애초부터 잘못된 dictionary order인 경우입니다
17+
위 경우는 단순 알파벳 비교로는 발견하기 어려우므로 flag를 사용하였습니다
18+
두번째는 result string의 길이가 input으로 주어진 단어들에 등장한 알파벳의 개수보다 적은 경우입니다
19+
이 경우는 nextLetters에 순환이 발생한 경우이므로 dictionary order가 잘못되었다고 판단할 수 있습니다
20+
Big O
21+
- N: 주어진 배열 words의 길이
22+
- S(W): 배열 words에 속한 모든 string의 길이의 총합
23+
- Time complexity: O(N + S(W))
24+
- prevLetterCounts와 nextLetters 생성 -> O(N)
25+
- nextLetters에 들어갈 알파벳 전후관계 찾기 -> O(S(W))
26+
- 알파벳 소문자의 수는 제한되어 있기 때문에 BFS의 시간 복잡도 상한선은 정해져 있습니다 -> O(26 * 26) = O(1)
27+
- Space complexity: O(1)
28+
- 알파벳 소문자의 수는 제한되어 있기 때문에 공간 복잡도의 상한선은 정해져 있습니다
29+
prevLetterCounts -> O(26) = O(1)
30+
nextLetters -> O(26 * 26) = O(1)
31+
queue -> O(26) = O(1)
32+
*/
33+
34+
import "strings"
35+
36+
func alienOrder(words []string) string {
37+
n := len(words)
38+
// prevLetterCounts[x] = count of letters y that are in relation of y->x
39+
prevLetterCounts := make(map[string]int)
40+
// nextLetters[x] = set of letters y that are in relation of x->y
41+
nextLetters := make(map[string]map[string]bool)
42+
for _, word := range words {
43+
for _, c := range word {
44+
if _, ok := prevLetterCounts[string(c)]; !ok {
45+
prevLetterCounts[string(c)] = 0
46+
nextLetters[string(c)] = make(map[string]bool)
47+
}
48+
}
49+
}
50+
51+
for i := 0; i < n-1; i++ {
52+
currWord := words[i]
53+
nextWord := words[i+1]
54+
// flag for edge case below
55+
diff := false
56+
for j := 0; j < len(currWord) && j < len(nextWord); j++ {
57+
if currWord[j] != nextWord[j] {
58+
diff = true
59+
if _, ok := nextLetters[string(currWord[j])][string(nextWord[j])]; !ok {
60+
prevLetterCounts[string(nextWord[j])]++
61+
nextLetters[string(currWord[j])][string(nextWord[j])] = true
62+
}
63+
break
64+
}
65+
}
66+
// tricky edge case!!!
67+
// if nextWord is prefix of currWord, then the provided dictionary order is invalid
68+
if !diff && len(currWord) > len(nextWord) {
69+
return ""
70+
}
71+
}
72+
// BFS
73+
queue := make([]string, 0, len(prevLetterCounts))
74+
for letter := range prevLetterCounts {
75+
// we can arrange letters whose prevLetterCount is zero as we wish
76+
if prevLetterCounts[letter] == 0 {
77+
queue = append(queue, letter)
78+
}
79+
}
80+
// in Go, using strings.Builder is the most efficient way to build strings
81+
var sb strings.Builder
82+
for len(queue) > 0 {
83+
// pop the letter from the queue and append it to the result string
84+
popped := queue[0]
85+
queue = queue[1:]
86+
sb.WriteString(popped)
87+
88+
for nextLetter := range nextLetters[popped] {
89+
prevLetterCounts[nextLetter]--
90+
// if prevLetterCount for nextLetter becomes zero, we can add it to the queue
91+
// append to the result string (order) in the next iteration
92+
if prevLetterCounts[nextLetter] == 0 {
93+
queue = append(queue, nextLetter)
94+
}
95+
}
96+
}
97+
// res is result string
98+
res := sb.String()
99+
// this case means that there was a cycle
100+
if len(res) != len(prevLetterCounts) {
101+
return ""
102+
}
103+
// else return res
104+
return res
105+
}

0 commit comments

Comments
 (0)