【备战秋招】每日一题:2022年9月23日-华为OD机试(第一题)-最佳检测顺序

文章介绍了为保证云存储服务的高可用性,采用主备模式并利用DFS检测服务状态。当服务故障发生时,系统需优先检测影响大的服务,并按编号升序进行。给出的解决方案涉及DFS计算节点的后继节点个数,然后按节点影响程度降序及编号升序排序。
摘要由CSDN通过智能技术生成

为了更好的阅读体检,可以查看我的算法学习网站
在线评测链接:P1163

题目内容

塔子哥有一家云存储服务提供商,他们家的核心产品是一个可扩展的分布式存储系统。他们的客户使用他们的服务来存储和管理各种数据,包括文档、图片、视频等。由于客户对数据的可靠性和可用性要求非常高,他们需要提供高可用性的存储服务,以确保在任何情况下都能保持服务的可用性。

为了实现高可用性,他们使用了主备模式来管理他们的存储系统。当主节点发生故障时,系统会自动将业务切换到备用节点。为了保证存储系统的稳定性,他们需要及时检测服务状态,并在必要时触发主备切换。

在存储系统中,不同的服务之间存在 依赖关系 ,每个服务最多只会依赖一个其他服务并且保证依赖不成环。例如,某些服务可能需要访问其他服务的数据才能正常工作。因此,当某个服务发生故障时,它所依赖的服务也会受到影响,可能导致更多的服务发生故障。

为了最大限度地减少服务故障对业务的影响,他们需要优先检测对业务影响大的服务,并按照 节点编号升序编排 检测顺序,现在请你帮忙解决一下这个问题。

注意: 如果业务影响相同时,则按节点编号大小升序编排。

输入描述

第一行输入两个整数 n n n 代表业务节点总个数。 ( 1 ≤ n ≤ 100000 ) (1 \leq n \leq 100000) (1n100000)

接下来一行 n n n个整数,第 i i i 个整数 f i f_i fi 代表$i $ 依赖 f i f_i fi ( i ∈ [ 0 , n − 1 ] ) (i \in [0,n-1]) (i[0,n1])

1.若 f i = − 1 f_i = -1 fi=1 , 则代表没有节点依赖

2.数据保证 f i ≠ i f_i \neq i fi=i

输出描述

一行 n n n个整数,以空格隔开,代表最终的排序结果.

样例

输入

5
-1 -1 1 2 3

输出

1 2 3 0 4

思路

简单dfs+排序

根据题目内容,我们可以提取3个关键信息:

1.一个点发生故障影响的是它的后继节点

2.后继节点越多,影响越大

3.影响相同的情况下,编号小的排前面

那么一种可行的做法就是:

先对每个点dfs一下,求每个点的后继结点个数。接着对节点按后继结点个数降序排序,相同的按编号升序排序。

类似题目推荐

本质就是考察了一下dfs和自定义排序。较为经典。

LeetCode
  1. 112. 路径总和
  2. 129. 求根到叶子节点数字之和
  3. 236. 二叉树的最近公共祖先
CodeFun2000

1.P1224 携程 2023.04.15-春招-第三题-魔法之树

2.P1196 华为实习 2023-04-19-第二题-塔子哥出城

3.P1159. 2022年清华大学(深圳)保研夏令营机试题-第一题-树上计数

4.P1190 华为实习 2023.04.12-第二题-获取最多食物

5.P1044. 拼多多内推笔试-2023.2.22.投喂珍珠

更多请见:知识点分类-训练-深度优先搜索专栏

代码

CPP

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n;
int fa[N];				//节点i的父节点
vector<int> e[N];		//节点i的子节点
int cnt[N];				//节点i的子树中的节点个数
int dfs(int s)		//计算s的 子树中的节点个数
{
	cnt[s] = 1;		//s本身
	for(int g: e[s])	//遍历s的每个子节点
	{
		cnt[s] += dfs(g);	//递归计算每个子节点的子树的节点个数,然后加到s上
	}
	return cnt[s];		//返回s的子树的节点个数
}
int ans[N];
bool cmp(int &x, int &y) {		//重写排序函数,按照子树中节点个数排序,相等时按大小排序
	if(cnt[x] == cnt[y]) return x < y;
	return cnt[x] > cnt[y];
}
int main()
{
	cin >> n;
	for(int i = 0 ; i < n ; i++) {
		cin >> fa[i];		//输入i的父节点
		if(fa[i] != -1) {		//-1代表i没有父节点,此时i为一棵树的根节点
			e[fa[i]].push_back(i);	//添加到父节点的子节点集合中
		}
	}
	for(int i = 0 ; i < n ; i++) {
		ans[i] = i;		//顺便初始化一下排序的值
		if(fa[i] == -1)	//i为一棵树的根节点,从这个点开始遍历
            dfs(i);
	}
	sort(ans, ans+n, cmp);	//排序
	for(int i = 0 ; i < n ; i ++) {
		cout << ans[i] << " \n"[i+1==n];	//输出," \n"[i+1==n]代表最后一次输出回车,前面输出空格分割
	}
}

python

from collections import defaultdict
n = int(input())
e = defaultdict(list)
a = list(map(int,input().split()))
# 读入 + 存图
for i in range(n):
	if a[i] != -1:
		e[a[i]].append(i)
# cnt 子树大小
cnt = [0] * n
# dfs 求子树大小
def dfs(u):
	cnt[u] = 1
	for v in e[u]:
		dfs(v)
		cnt[u] += cnt[v]
# 按题目要求排序
res = []
for i in range(n):
	if cnt[i] == 0:
		dfs(i)
	res.append([i , cnt[i]])
res.sort(key = lambda x : (-x[1] , x[0]))
print(" ".join(map(str,[x[0] for x in res])))

Java

import java.lang.reflect.Array;
import java.util.*;

public class Main {
    static int N = 100010;
    static int n;
    static Map<Integer, List<Integer>> graph = new HashMap<>();
    static boolean[] visited = new boolean[N];
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        for(int i = 0; i < N; i++){
            graph.put(i, new ArrayList<>());
        }
        // 读入,存图
        for(int i = 0; i < n; i++){
            int temp = in.nextInt();
            if(temp != -1){
                add(temp, i);
            }
        }
        // dfs 求后继结点个数 , 以二元组形式存储,以便之后的自定义排序
        List<int[]> res = new ArrayList<>();
        for(int i = 0; i < n; i++){
            res.add(new int[]{dfs(i), i});
        }
        // 自定义排序
        Collections.sort(res, new Comparator<int[]>() {
            @Override
            public int compare(int[] a, int[] b) {
                // 根据题解,优先按后继结点个数降序排序
                if (a[0] != b[0]) {
                    return b[0] - a[0];
                } else { // 相同情况下 按节点编号升序排序
                    return Integer.compare(a[1], b[1]);
                }
            }
        });
        // 输出答案
        for(int[] pair : res){
            System.out.print(pair[1] + " ");
        }
    }
    // 计算后继节点个数
    private static int dfs(int i){
        int count = 0;
        for(int j : graph.get(i)){
            count += dfs(j);
        }
        return count+1;
    }
    private static void add(int a, int b){
        graph.get(a).add(b);
    }
}
// bt  guanam

Go

package main

import (
	"fmt"
	"sort"
)

const N int = 1e5 + 10

var f []int = make([]int, N)
var n int

type node struct {
	id     int
	sonCnt int
}

func dfs(edges [][]int, x int) int {
	if len(edges[x]) == 0 {
		return 1
	}
	res := 1
	for _, v := range edges[x] {
		res += dfs(edges, v)
	}
	return res
}

func main() {
	//构建有向图 计算每个节点的子节点个数 排序
	fmt.Scan(&n)
	//有向图的邻接边
	edges := make([][]int, n)
	for i := range edges {
		edges[i] = make([]int, 0)
	}
	outDegree := make([]int, n) //每个节点的出度
	for i := 0; i < n; i++ {
		fmt.Scan(&f[i])
		if f[i] != -1 {
			edges[f[i]] = append(edges[f[i]], i)
			outDegree[f[i]]++
		}
	}
	cnt := make([]int, n)
	for i := 0; i < n; i++ {
		if outDegree[i] == 0 { //如果出度为0 子节点就只算它自己
			cnt[i] = 1
		} else { //出度不为0 dfs它的所有直接子节点 更新它的总子节点个数
			cnt[i] = dfs(edges, i)
		}
	}
	nodes := make([]node, n)
	for i := 0; i < n; i++ {
		nodes[i].id = i
		nodes[i].sonCnt = cnt[i]
	}
	sort.Slice(nodes, func(i, j int) bool {
		if nodes[i].sonCnt != nodes[j].sonCnt { //子节点数量不同时 按照子节点数量降序排列
			return nodes[i].sonCnt > nodes[j].sonCnt
		}
		return nodes[i].id < nodes[j].id //相同时 按照编号升序排列
	})
	for i := 0; i < n; i++ {
		fmt.Printf("%d", nodes[i].id)
		if i != n-1 {
			fmt.Printf(" ")
		}
	}
}
// by xchen

Js

// 创建 readline interface 实例
const rl = require('readline').createInterface({input: process.stdin});

// 获取 readline 迭代器
const iter = rl[Symbol.asyncIterator]();

// 定义异步函数,并使用 IIFE 函数立即执行
void async function () {
    // 读取第一行输入,n 代表节点个数
    const n = parseInt(await readline());

    // 读取第二行输入,F 代表以空格分隔的每个节点的父节点编号
    const F = (await readline()).split(' ').map(item => parseInt(item));

    // 创建 Map,存放每个节点的父节点后继位置的数组
    const map = new Map();

    // 定义数组存放每个节点所对应的后继结点个数
    const count = [];

    // 遍历每个节点,判断其父节点是否存在,如果存在,则将该节点下标加入其对应的数组中,否则跳过
    for (let i = 0; i < n; i++) {
        if (F[i] !== -1) {
            if (map.has(F[i])) {
                map.set(F[i], [...map.get(F[i]), i]);   // 将该节点下标 push 进去其父节点后继位置的数组中
            }
            else {
                map.set(F[i], [i]);    // 新建一个数组,存放当前节点下标,指定其为其父节点的后继位置
            }
        }
    }

    // 定义深度优先遍历函数,参数分别为 map 和 currV,返回值为当前节点的后继结点个数
    const dfs = (map, currV) => {
        if (!map.has(currV) || map.get(currV) === 0) {   // 当前节点不存在或其没有后继结点,返回 1
            return 1;
        }
        let res = 1;   // res 表示当前节点及其后继结点的总个数
        const edge = map.get(currV);   // 获取当前节点的后继位置数组
        for (let V of edge) {
            res += dfs(map, V);   // 递归计算每个后继结点的后继结点总个数,并将结果加到 res 上
        }
        return res;   // 返回当前节点及其后继结点的总个数
    }

    // 定义二维数组 comp 存放每个节点的下标和其所对应的后继结点个数
    const comp = new Array(n).fill(0).map(item => new Array(2).fill(0));
    const res = [];

    // 遍历每个节点,计算其所对应的后继结点总数并存入 comp 中
    for (let i = 0; i < n; i++) {
        comp[i][0] = i;   // 第一列存放节点下标
        comp[i][1] = dfs(map, i);   // 第二列存放节点的后继结点总个数
    }

    // 对二维数组 comp 进行排序,先按第二列倒序排序,之后按第一列顺序排序
    comp.sort((a, b) => {
        if (a[1] === b[1]) {
            return a[0] - b[0];
        }
        else {
            return b[1] - a[1];
        }
    })

    // 将排序后的节点下标存入 res 数组中
    for (let pair of comp) {
        res.push(pair[0])
    }

    // 输出 res 数组中的元素,以空格分隔
    console.log(res.join(' '))
}()

题目内容均收集自互联网,如如若此项内容侵犯了原著者的合法权益,可联系我: (CSDN网站注册用户名: 塔子哥学算法) 进行删除。

华为OD机试 数大雁是华为公司招聘流程中的其中一项测试环节,用于考察应聘者在编程方面的能力。该笔试主要考察的是计算机基础知识和编程实战能力。 华为OD机试目通常由一系列算法编程组成,其中涵盖了数据结构和算法、字符串操作、动态规划、图论、递归、搜索等各个方面的知识点。考生需要根据目要求,自行设计算法并编写相应的代码,来解决问。 这一环节的目的是为了考察应聘者在编程方面的能力,包括对算法的理解和实现能力、代码质量和效率等方面的评估。编程能力在今天的软件工程领域中十分重要,能够有效地解决实际问,提高代码的可读性和可维护性,是评估一个程序员水平的重要指标。 参加华为OD机试数字大雁,需要具备扎实的计算机基础知识,熟练掌握编程语言和常用的数据结构和算法,并具备理解和分析问的能力。在备战笔试的过程中,应聘者应该注重对算法的学习和理解,并进行大量的编程实践,深入理解各类算法的原理与应用场景。在解答算法时,可以运用递归、分治、贪心、动态规划等常用思想,将问拆解为更小的子问,从而更好地解决复杂的算法华为OD机试数字大雁是一个对程序员编程能力的一种考察方式,参加者需要通过编写代码解决目,展示自己的编程实力。准备过程中,应聘者应该注意提高自己的算法能力,并进行足够的练习,积累编程经验,从而顺利通过华为OD机试数字大雁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值