1. 问题描述:
有 N 个盘子,每个盘子上写着一个仅由小写字母组成的英文单词。你需要给这些盘子安排一个合适的顺序,使得相邻两个盘子中,前一个盘子上单词的末字母等于后一个盘子上单词的首字母。请你编写一个程序,判断是否能达到这一要求。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。每组数据第一行包含整数 N,表示盘子数量。接下来 N 行,每行包含一个小写字母字符串,表示一个盘子上的单词。一个单词可能出现多次。
输出格式
如果存在合法解,则输出"Ordering is possible.",否则输出"The door cannot be opened."。
数据范围
1 ≤ N ≤ 10 ^ 5,
单词长度均不超过1000
输入样例:
3
2
acm
ibm
3
acm
malform
mouse
2
ok
ok
输出样例:
The door cannot be opened.
Ordering is possible.
The door cannot be opened.
来源:https://www.acwing.com/problem/content/1187/
2. 思路分析:
对于字符串首尾字符有连接关系的建图问题有一个常见的建图技巧:根据题目的要求,将字符串的前面的字符与结尾的字符分别看成是一个点,对于这道题目来说因为涉及到首尾一个字符,所以我们可以将每一个单词的首字母与尾字母看成是一个点,对于每一个单词来说首字母向尾字母连一条有向边,注意这里是有向边(因为字符串a可以接在字符串b的后面,但是反过来不一定行所以需要建有向边),抽象出具体的模型之后那么最多有26个点,n条边,我们能否在图中找到一条路径使得可以依次走每一条边,所以属于经典的欧拉路径(欧拉回路)的问题,也即判断图中是否存在欧拉路径(欧拉回路):
- 除了起点和终点之外,其余点的入度 = 出度
- 所有点都是连通的,可以使用dfs,bfs或者是并查集检验连通性
我们可以声明din和dout数组来统计每一个节点的入度和出度,在输入每一个单词的时候统计节点的出度和入度,使用start和end来记录起点和终点的情况,我们可以遍历26个节点,对于入度不等于出度的情况,如果当前节点的出度 = 入度 + 1,说明是起点,那么start + 1,如果入度 = 出度 + 1说明是终点那么end + 1,如果不属于这两种情况说明不存在欧拉路径或者欧拉回路,使用一个变量success来记录判断的对应情况,一开始的时候为1,如果中间检查的过程中发现存在不符合的条件那么将其置为0,退出循环,最终检验一个每一个标记的节点是否在同一个集合中,如果在同一个集合中说明整个图是连通的,输出对应的字符串即可。
3. 代码如下:
from typing import List
class Solution:
# 寻找节点x的父节点
def find(self, x: int, fa: List[int]):
if x != fa[x]:
fa[x] = self.find(fa[x], fa)
return fa[x]
def process(self):
T = int(input())
for i in range(T):
n = int(input())
# 最多有26个节点, 声明大一点的列表
# 并查集的fa列表, 用来检验连通性
fa = [i for i in range(30)]
din, dout, st = [0] * 30, [0] * 30, [0] * 30
for i in range(n):
s = input()
a, b = ord(s[0]) - ord("a"), ord(s[-1]) - ord("a")
dout[a] += 1
din[b] += 1
fa[self.find(a, fa)] = self.find(b, fa)
# 标记当前位置有边
st[a] = st[b] = 1
success = 1
start = end = 0
for i in range(26):
if din[i] != dout[i]:
if din[i] == dout[i] + 1:
# 终点的个数加1
end += 1
elif dout[i] == din[i] + 1:
# 起点的个数加1
start += 1
else:
# 不满足欧拉路径或者欧拉回路的条件
success = 0
break
# 起点和终点是同一个点或者起点和终点都只有一个点那么说明是满足条件的如果都不属于这两种情况那么说明不满足条件不存在欧拉路径或者欧拉回路
if success and not ((start == 0 and end == 0) or start == 1 and end == 1): success = 0
p = -1
for i in range(26):
# 当前节点有出边
if st[i]:
# 检验所有的点是否在同一个集合中
t = self.find(i, fa)
if p == -1:
# 第一次访问
p = t
elif p != t:
success = 0
break
if success:
print("Ordering is possible.")
else:
print("The door cannot be opened.")
if __name__ == "__main__":
Solution().process()