【华为OD-E卷 - 服务失效判断 100分(python、java、c++、js、c)】
题目
某系统中有众多服务,每个服务用字符串(只包含字母和数字,长度<=10)唯一标识,服务间可能有依赖关系,如A依赖B,则当B故障时导致A也故障。
依赖具有传递性,如A依赖B,B依赖C,当C故障时导致B故障,也导致A故障。
给出所有依赖关系,以及当前已知故障服务,要求输出所有正常服务。
依赖关系:服务1-服务2 表示"服务1"依赖"服务2"
不必考虑输入异常,用例保证:依赖关系列表、故障列表非空,且依赖关系数,故障服务数都不会超过3000,服务标识格式正常。
输入描述
- 半角逗号分隔的依赖关系列表(换行)
半角逗号分隔的故障服务列表
输出描述
- 依赖关系列表中提及的所有服务中可以正常工作的服务列表,用半角逗号分隔,按依赖关系列表中出现的次序排序。
特别的,没有正常节点输出单独一个半角逗号
用例
用例一:
输入:
a1-a2,a5-a6,a2-a3
a5,a2
输出:
a6,a3
用例二:
输入:
a1-a2
a2
输出:
,
python解法
- 解题思路:
- 问题分析:
给定一些服务之间的依赖关系,并且一些服务出现了故障。我们需要找出没有故障且可以正常工作的服务。服务之间的依赖关系通过"服务-所需服务"对给出。如果一个服务有故障,所有依赖它的服务也会受到影响,不能正常工作。
任务是根据给定的依赖关系和故障服务,计算哪些服务仍然能正常工作。
关键概念:
依赖图:服务间的依赖关系可以通过有向图表示。一个服务依赖另一个服务,可以认为它指向该服务。
广度优先搜索(BFS):从所有有故障的服务开始,逐步查找所有受影响的服务。通过这种方式,我们可以找到哪些服务因为故障而不能正常工作。
拓扑排序:服务的正常工作有一定的顺序,我们要按服务出现的顺序返回结果。
步骤:
构建依赖关系图:从输入的依赖对中构建依赖图,其中每个服务可能有多个依赖服务。
标记故障服务:初始化一个故障队列,将所有故障的服务加入队列,并标记为已访问。
广度优先搜索:从所有故障服务开始,逐步查找所有受其影响的服务,即故障服务依赖的服务。
筛选活动服务:找到没有被访问到的服务,这些服务不受故障的影响。
按顺序输出:按照服务出现的顺序输出可正常工作的服务。
python
# 从输入中读取服务依赖关系对和故障服务
dependencies = [pair.split("-") for pair in input().split(",")] # 解析服务依赖关系
faults = set(input().split(",")) # 获取故障服务集合
def find_services():
# 初始化依赖关系图和服务的排序字典
dep_graph = {} # 存储每个服务的依赖服务集合
order = {} # 存储服务的出现顺序,用于排序输出
index = 0 # 用来给服务分配唯一的顺序编号
for dep, required in dependencies:
# 为每个依赖关系建立图,所需服务(required)指向依赖它的服务(dep)
if required not in dep_graph:
dep_graph[required] = set() # 如果所需服务还没被添加,则初始化为空集合
dep_graph[required].add(dep) # 将依赖的服务添加到该所需服务的集合中
if dep not in dep_graph:
dep_graph[dep] = set() # 如果服务(dep)没有依赖关系,则初始化为空集合
# 给每个服务分配一个唯一的顺序编号
if dep not in order:
order[dep] = index
index += 1
if required not in order:
order[required] = index
index += 1
# 初始化队列,将所有故障服务加入队列
queue = list(faults)
visited = set(queue) # 记录已经访问过的服务,避免重复访问
# 广度优先搜索,从所有故障服务开始,查找受影响的服务
while queue:
fault = queue.pop(0) # 弹出队列中的一个故障服务
if fault in dep_graph:
# 遍历所有依赖故障服务的服务,并将其加入队列
for dependent in dep_graph.pop(fault, []):
if dependent not in visited:
queue.append(dependent) # 将依赖的服务加入队列
visited.add(dependent) # 标记该服务为已访问
# 遍历所有服务,找出那些没有被访问过的服务
active_services = [svc for svc in order if svc not in visited]
# 如果没有可用的服务,返回空字符串
if not active_services:
return ","
# 返回按顺序排序的活动服务,排序根据它们的出现顺序
return ",".join(sorted(active_services, key=lambda x: order[x]))
# 输出最终结果
print(find_services())
java解法
- 解题思路
- 问题分析:
我们有多个服务之间的依赖关系和一些故障的服务。每个服务可能依赖于其他服务,并且如果一个服务故障了,所有依赖它的服务也会受到影响,不能正常工作。
任务是找出哪些服务在给定的故障服务集和依赖关系下仍然能够正常工作。
关键概念:
依赖关系:每个服务可能依赖于其他服务。我们可以将这些依赖关系看作一个有向图,其中每个服务为一个节点,依赖关系为有向边。
故障传播:故障是从已故障的服务开始的,所有依赖于这些服务的服务都会受影响。
拓扑排序:服务之间有依赖顺序,输出时要按照服务的顺序排列。
步骤:
解析输入:解析依赖关系和故障服务。
构建依赖图:根据依赖关系构建一个图,记录每个服务依赖的服务。
故障传播:从故障服务开始,使用深度优先搜索(DFS)或者栈来传播故障,标记所有受影响的服务。
筛选正常工作服务:所有没有被故障影响的服务才是正常工作的服务。
按顺序输出:输出正常工作的服务,并根据它们的顺序排序
java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读取并解析服务依赖关系
String relInput = sc.nextLine();
List<String> relPairs = split(relInput, ','); // 将依赖关系对按逗号分割
// 创建存储依赖关系的列表,将每一对服务存入
List<Pair<String, String>> rels = new ArrayList<>();
for (String pStr : relPairs) {
List<String> p = split(pStr, '-'); // 将每个依赖对按"-"分割
rels.add(new Pair<>(p.get(0), p.get(1))); // 存储为 (服务1, 服务2) 的形式
}
// 读取故障服务列表
String failInput = sc.nextLine();
List<String> fails = split(failInput, ','); // 将故障服务按逗号分割
// 输出结果:哪些服务是可用的
System.out.println(getActive(rels, fails));
}
// 将输入的字符串按指定字符分割成列表
public static List<String> split(String s, char d) {
return Arrays.asList(s.split(String.valueOf(d))); // 返回按指定分隔符分割的列表
}
public static String getActive(List<Pair<String, String>> rels, List<String> fails) {
// 存储依赖关系的图:每个服务依赖的服务
Map<String, Set<String>> depMap = new HashMap<>();
// 存储故障服务的集合,用于快速查找
Set<String> failSet = new HashSet<>(fails);
// 存储服务的顺序,用于输出时排序
Map<String, Integer> orderMap = new HashMap<>();
// 用一个整数索引为每个服务分配一个唯一顺序
int idx = 0;
for (Pair<String, String> rel : rels) {
String dep = rel.getKey(); // 依赖的服务
String prov = rel.getValue(); // 提供服务的服务
// 构建依赖图:每个提供服务指向依赖它的服务
depMap.computeIfAbsent(prov, k -> new HashSet<>()).add(dep);
// 给每个服务分配一个顺序
orderMap.putIfAbsent(dep, idx++);
orderMap.putIfAbsent(prov, idx++);
}
// 从故障服务开始,传播故障
for (String fail : fails) {
Stack<String> stk = new Stack<>();
stk.push(fail); // 将故障服务加入栈中
// 广度优先/深度优先传播故障
while (!stk.isEmpty()) {
String svc = stk.pop(); // 弹出栈顶服务
if (depMap.containsKey(svc)) { // 如果该服务有依赖的服务
for (String dep : depMap.get(svc)) { // 遍历它的依赖
if (!failSet.contains(dep)) { // 如果依赖的服务尚未标记为故障
failSet.add(dep); // 标记该服务为故障
stk.push(dep); // 将该服务加入栈中继续传播
}
}
depMap.remove(svc); // 删除已处理的服务的依赖关系
}
}
}
// 存储最终可以正常工作的服务
List<String> operList = new ArrayList<>();
// 遍历所有服务,选择那些没有被标记为故障的服务
for (Map.Entry<String, Integer> entry : orderMap.entrySet()) {
if (!failSet.contains(entry.getKey())) {
operList.add(entry.getKey()); // 添加可用的服务
}
}
// 如果没有可用服务,返回空字符串
if (operList.isEmpty()) {
return ",";
}
// 按照服务的顺序进行排序
operList.sort(Comparator.comparingInt(orderMap::get));
// 输出所有可用服务,按顺序连接为字符串
return String.join(",", operList);
}
// 一个简单的 Pair 类,用来存储服务依赖对
public static class Pair<K, V> {
private final K key; // 服务1
private final V value; // 服务2
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key; // 获取服务1
}
public V getValue() {
return value; // 获取服务2
}
}
}
C++解法
- 解题思路
- 该问题的核心是模拟一组服务的依赖关系,并根据故障服务的传播,找出最终哪些服务是可用的。故障从故障服务开始传播,影响到所有依赖于该服务的其他服务。最后,我们需要输出剩余可用的服务,并按照它们的出现顺序排序。
步骤概述:
输入解析:
输入包含两部分:服务的依赖关系和故障服务。
服务依赖关系是"服务1-服务2"的形式,表示服务2依赖服务1。
故障服务是一个以逗号分隔的字符串,表示这些服务已经失败。
构建依赖图:
使用map<string, set> dependencyMap来存储依赖关系图,其中dependencyMap[provider]表示提供服务的服务所依赖的服务集合。
使用map<string, int> appearanceOrder来为每个服务分配一个顺序编号,以便我们在最后根据顺序输出。
故障传播:
对每个故障服务,通过栈模拟深度优先搜索(DFS)来标记所有因故障服务而故障的服务。
如果服务依赖于故障服务,它也会故障。继续传播,直到所有受影响的服务都被标记为故障。
筛选正常服务:
遍历所有服务,选出那些没有被故障影响的服务。
输出:
对所有剩余的正常服务按照它们的顺序进行排序,然后按顺序输出
cpp
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <algorithm>
#include <sstream>
using namespace std;
// 字符串分割函数,将输入字符串按指定分隔符切割成多个子串
vector<string> split(const string& s, char delimiter) {
vector<string> tokens;
string token;
istringstream tokenStream(s); // 使用istringstream将字符串转换为流
while (getline(tokenStream, token, delimiter)) { // 使用getline按分隔符读取
tokens.push_back(token); // 存入tokens列表
}
return tokens;
}
// 根据服务依赖关系和故障服务计算可用的服务
string determineActiveServices(const vector<pair<string, string>>& relations, const vector<string>& failures) {
map<string, set<string>> dependencyMap; // 存储服务的依赖关系图
set<string> failureSet(failures.begin(), failures.end()); // 存储所有故障服务
map<string, int> appearanceOrder; // 存储服务的出现顺序,便于排序输出
int index = 0;
// 构建服务的依赖图及其顺序
for (const auto& relation : relations) {
const string& dependent = relation.first; // 依赖的服务
const string& provider = relation.second; // 提供服务的服务
// 将提供服务的服务的依赖关系加入依赖图
dependencyMap[provider].insert(dependent);
// 给服务分配顺序
if (appearanceOrder.find(dependent) == appearanceOrder.end()) {
appearanceOrder[dependent] = index++;
}
if (appearanceOrder.find(provider) == appearanceOrder.end()) {
appearanceOrder[provider] = index++;
}
}
// 对每个故障服务,进行故障传播
for (const auto& failure : failures) {
stack<string> stack; // 使用栈模拟DFS
stack.push(failure); // 将故障服务压入栈中
// 深度优先传播故障
while (!stack.empty()) {
string service = stack.top(); // 获取栈顶的服务
stack.pop(); // 弹出栈顶服务
if (dependencyMap.find(service) != dependencyMap.end()) {
// 如果服务有依赖的服务,传播故障
for (const auto& dependent : dependencyMap[service]) {
if (failureSet.find(dependent) == failureSet.end()) {
failureSet.insert(dependent); // 标记该服务为故障
stack.push(dependent); // 将该服务加入栈中继续传播
}
}
dependencyMap.erase(service); // 删除已处理的服务,避免重复处理
}
}
}
// 筛选正常工作且未故障的服务
vector<string> operationalList;
for (const auto& entry : appearanceOrder) {
if (failureSet.find(entry.first) == failureSet.end()) {
operationalList.push_back(entry.first); // 如果服务没有故障,则加入结果列表
}
}
// 如果没有可用服务,返回空字符串
if (operationalList.empty()) {
return ",";
}
// 按照顺序进行排序
sort(operationalList.begin(), operationalList.end(), [&](const string& a, const string& b) {
return appearanceOrder[a] < appearanceOrder[b]; // 按照服务出现的顺序排序
});
// 拼接并返回结果
string result = operationalList[0];
for (size_t i = 1; i < operationalList.size(); ++i) {
result += "," + operationalList[i];
}
return result;
}
int main() {
string relationsInput;
getline(cin, relationsInput); // 读取依赖关系输入
vector<string> relationsPairs = split(relationsInput, ','); // 按逗号分割依赖对
vector<pair<string, string>> relations;
// 将每个依赖对存入relations列表
for (const auto& pairStr : relationsPairs) {
vector<string> pair = split(pairStr, '-'); // 按"-"分割
relations.emplace_back(pair[0], pair[1]); // 存入依赖关系
}
string failuresInput;
getline(cin, failuresInput); // 读取故障服务输入
vector<string> failures = split(failuresInput, ','); // 按逗号分割故障服务
// 输出最终可用的服务
cout << determineActiveServices(relations, failures) << endl;
return 0;
}
C解法
解题思路
c
更新中
JS解法
解题思路
- 该问题的核心思想是根据服务的依赖关系和故障服务,计算哪些服务依然是可用的。故障服务会导致所有依赖于它的服务也不可用,因此需要传播故障,直到所有受影响的服务都被标记为故障。最终,输出剩余可用的服务,并按照它们出现的顺序排列。
主要步骤:
解析输入数据:首先解析依赖关系和故障服务的输入。
构建服务依赖图:根据输入的依赖关系,构建一个图,其中每个服务对应着它依赖的服务。
故障传播:对于每个故障服务,递归地标记所有受影响的服务。
筛选可用服务:在传播完所有故障后,筛选出那些未被标记为故障的服务,并按它们出现的顺序输出。
javascript
const readline = require("readline");
// 创建读取接口,用于从标准输入读取数据
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
// 用于存储输入的每一行
const ln = [];
// 当读取到每一行输入时,执行以下回调函数
rl.on("line", (line) => {
ln.push(line); // 将每行输入存储到数组ln中
// 当输入两行数据时,进行处理
if (ln.length === 2) {
// 第1行是依赖关系,按逗号分割后每个依赖对按连字符分割,得到依赖关系数组
const rel = ln[0].split(",").map((p) => p.split("-"));
// 第2行是故障服务列表,按逗号分割得到故障服务数组
const brk = ln[1].split(",");
// 计算并输出可用的服务
console.log(getNorm(rel, brk));
// 清空输入行数组,准备下一轮输入
ln.length = 0;
}
});
// 获取剩余可用服务的函数
function getNorm(rel, brk) {
const nxt = {}; // 存储服务之间的依赖关系(依赖图)
const fst = {}; // 存储服务的出现顺序(用于排序)
let idx = 0;
// 构建依赖图和服务顺序映射
for (let [c, f] of rel) {
// 确保每个服务有对应的依赖集合
if (!nxt[c]) nxt[c] = new Set();
if (!nxt[f]) nxt[f] = new Set();
// f依赖于c,加入依赖图
nxt[f].add(c);
// 如果服务没有顺序,则分配一个顺序编号
if (!fst[c]) fst[c] = idx++;
if (!fst[f]) fst[f] = idx++;
}
// 对于每个故障服务,递归地删除所有受其影响的服务
for (let s of brk) {
del(nxt, s); // 递归删除故障服务及其依赖的服务
}
// 依赖图nxt中剩余的服务即为可用服务
const res = Object.keys(nxt); // 获取所有剩余的服务
if (res.length == 0) return ","; // 如果没有可用服务,返回逗号表示空
// 按照服务的出现顺序排序后返回
return res.sort((a, b) => fst[a] - fst[b]).join(",");
}
// 递归删除故障服务及其所有依赖服务的函数
function del(nxt, s) {
if (nxt[s]) {
const rm = nxt[s]; // 获取依赖于故障服务的所有服务
delete nxt[s]; // 删除故障服务
// 递归删除所有依赖于该服务的服务
for (let ss of rm) {
del(nxt, ss);
}
}
}
注意:
如果发现代码有用例覆盖不到的情况,欢迎反馈!会在第一时间修正,更新。
解题不易,如对您有帮助,欢迎点赞/收藏