Post

LC 269 - Alien Dictionary

LC 269 - Alien Dictionary

Question

There is a new alien language that uses the English alphabet. However, the order of the letters is unknown to you.

You are given a list of strings words from the alien language’s dictionary. Now it is claimed that the strings in words are sorted lexicographically by the rules of this new language.

If this claim is incorrect, and the given arrangement of string in words cannot correspond to any order of letters, return "".

Otherwise, return a string of the unique letters in the new alien language sorted in lexicographically increasing order by the new language’s rules__. If there are multiple solutions, return any of them.

Example 1:

1
2
Input: words = ["wrt","wrf","er","ett","rftt"]
Output: "wertf"

Example 2:

1
2
Input: words = ["z","x"]
Output: "zx"

Example 3:

1
2
Input: words = ["z","x","z"]
Output: ""

Explanation: The order is invalid, so return "".

Constraints:

  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 100
  • words[i] consists of only lowercase English letters.

Question here and solution here

Solution

concept

This question is actually a graph problem that needs topological sort. We need to arrange the problem into a graph by choosing the right representation of the adjacency list. For example, focus on the green example below for A, B, B: we have to compare the word and digit and get the first different char and then from there construct the adjacency list. We then can use the same technique in 210. Course Schedule II for topological sorting. Take note that this is post-order DFS and this is the way for topological sort, we just need to reverse the answer in the end. Note that we do not need to reverse in 210. Course Schedule II because in that question we actually need to return the answer in reverse as by the question.

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class Solution:
    def alienOrder(self, words: List[str]) -> str:
        adj_list = {c:set() for w in words for c in w}
		# construct adj list
		# key: char
        # val: set of char that is lexicographically behind the key according to words given
        for i in range(len(words) - 1):
            w1, w2 = words[i], words[i+1]
            min_len = min(len(w1), len(w2))
            if w1[:min_len] == w2[:min_len] and len(w1) > len(w2): # and also w1 is before w2 in pos
                return ""
            for j in range(min_len):
                if w1[j] != w2[j]:
                    adj_list[w1[j]].add(w2[j])
                    break
        
        ans = []
        visited = set() # current path
        processed = set() # already added into ans

        def dfs(c):
            if c in visited:
                return False # detected cycle
            if c in processed:
                return True # processed 

            visited.add(c)
            for nei in adj_list[c]:
                if not dfs(nei):
                    return False # found cycle
            visited.remove(c)
            processed.add(c)
            ans.append(c)
            return True

        for c in adj_list.keys():
            if not dfs(c):
                return ""
        
        ans.reverse()

        return "".join(ans)
        
        
class NeetSolution:
	"""
	mostly same as above
	but he only used one set to represent 3 state
	"""
    def foreignDictionary(self, words: List[str]) -> str:
        adj = {c: set() for w in words for c in w}

        for i in range(len(words) - 1):
            w1, w2 = words[i], words[i + 1]
            minLen = min(len(w1), len(w2))
            if len(w1) > len(w2) and w1[:minLen] == w2[:minLen]:
                return ""
            for j in range(minLen):
                if w1[j] != w2[j]:
                    adj[w1[j]].add(w2[j])
                    break
		# False: processed; True: visited and it is in current path -> lead to cyclic; Not found: not visited yet
        visited = {}
        res = []

        def dfs(char):
            if char in visited:
                return visited[char]

            visited[char] = True

            for neighChar in adj[char]:
                if dfs(neighChar):
                    return True

            visited[char] = False
            res.append(char)

        for char in adj:
            if dfs(char):
                return ""

        res.reverse()
        return "".join(res)

Complexity

time: $O(N+V+E)$
space: $O(V + E)$
where V is the number of unique char, E is the number of edges and N is the sum length of all the strings.

This post is licensed under CC BY 4.0 by the author.