【Py/Java/C++三种语言OD独家2024E卷真题】20天拿下华为OD笔试之【DFS/BFS】2024E-BOSS的收入【欧弟算法】全网注释最详细分类最全的华为OD真题题解

可上 欧弟OJ系统 练习华子OD、大厂真题
绿色聊天软件戳 od1441了解算法冲刺训练(备注【CSDN】否则不通过)

文章目录

  • 相关推荐阅读
  • 题目描述与示例
    • 题目描述
    • 输入描述
    • 输出描述
    • 补充说明
    • 示例
      • 输入
      • 输出
  • 解题思路
    • 拓扑排序BFS解法
    • *自底向上的DFS解法
  • 代码
    • 解法一:拓扑排序BFS
      • python
      • java
      • cpp
      • 时空复杂度
    • *解法二:自底向上的DFS
      • python
      • java
      • cpp
      • 时空复杂度
  • 华为OD算法/大厂面试高频题算法练习冲刺训练

相关推荐阅读

  • 【华为OD机考】2024D+E卷最全真题【完全原创题解 | 详细考点分类 | 不断更新题目】
  • 【华为OD笔试】2024D+E卷机考套题汇总【真实反馈,不断更新,限时免费】
  • 【华为OD笔试】2024D卷命题规律解读【分析300+场OD笔试考点总结】

从2024年8月14号开始,OD机考全部配置为2024E卷
注意几个关键点:

  1. 大部分的题目仍为往期2023A+B+C以及2024D的旧题。注意万变不离其宗,把方法掌握,无论遇到什么题目都可以轻松应对。
  2. 支持多次提交题目,以最后一次提交为准。可以先做200的再做100的,然后可以反复提交。
  3. E卷仍然为单机位+屏幕监控的形式进行监考。
  4. 进入考试界面新加入了这样一段话并且用红字标出,可以看出华子对作弊代考等行为是0容忍的,请各位同学认真学习,不要妄图通过其他违规途径通过考试。
    在这里插入图片描述

【Py/Java/C++三种语言OD独家2024E卷真题】20天拿下华为OD笔试之【DFS/BFS】2024E-BOSS的收入【欧弟算法】全网注释最详细分类最全的华为OD真题题解_第1张图片

题目描述与示例

题目描述

一个XX产品行销总公司,只有一个boss,其有若干一级分销,一级分销又有若干二级分销,每个分销只有唯一的上级分销。规定,每个月,下级分销需要将自己的总收入(自己的+下级上交的)每满100元上交15元给自己的上级。

现给出一组分销的关系,和每个分销的收入,请找出boss并计算出这个boss的收入。

比如:

收入100元,上交15元;

收入199元(99元不够100),上交15元,

收入200元,上交30元。

输入:

分销关系和收入:[[分销id 上级分销的Id 收入],[分销id 上级分销的id 收入],[分销id 上级分销的id 收入]]

分销ID范围0..65535

收入范围0..65535,单位元

提示:输入的数据只存在1个boss,不存在环路

输出:[boss的ID,总收入]

输入描述

1行输入关系的总数量N

2行开始,输入关系信息,格式:分销ID 上级分销ID 收入

比如:

5
1 0 100
2 0 199
3 0 200
4 0 200
5 0 200

输出描述

输出:boss的ID 总收入

比如:

0 120

补充说明

给定的输入数据都是合法的,不存在环路,重复的

示例

输入

5
1 0 100
2 0 199
3 0 200
4 0 200
5 0 200

输出

0 120

解题思路

拓扑排序BFS解法

很明显这个层层分销的制度,可以使用树形结构来表示。譬如对于例子

5
1 0 100
2 0 199
3 1 200
4 1 200
5 2 200

可以画成如下树形结构

暂时无法在飞书文档外展示此内容

每一个上级的收入不仅取决于他自己的收入,还取决于其直接下属的收入。

以这个例子为例,如果我们想计算节点0从节点1得到多少收入,就必须先计算节点1的总收入。

而如果想计算节点1的总收入,又必须先计算节点1从节点3和节点4分别获得多少收入。

很显然这存在依赖关系:我们必须先把下层节点的总收入计算完之后,才能将总收入进行抽成,来计算当前节点的的总收入

对于这种存在依赖的问题,我们可以使用拓扑排序来完成。直接套模板即可完成。

注意本题并没有直接告知根节点的ID,因此需要找到唯一的非子节点来作为根节点。

*自底向上的DFS解法

当然,熟悉树和递归的同学,也容易想到能够用自底向上的****DFS来完成这个题目。

本文不做赘述,后面提供包含详尽注释的代码。

代码

解法一:拓扑排序BFS

python

# 题目:【BFS】2024E-BOSS的收入
# 分值:200
# 作者:许老师-闭着眼睛学数理化
# 算法:BFS/拓扑排序
# 代码看不懂的地方,请直接在群上提问

from collections import defaultdict, deque

# 输入边的个数
n = int(input())

# 构建邻接表,key是子节点,value是父节点
# 由于每一个节点最多只有一个父节点
# 所以parents邻接表的value无需设置默认值为list
parents = dict()

# 构建总收入哈希表,key是节点名,value是该节点的收入
total_money = defaultdict(int)

# 构建入度哈希表,key是节点名,value是入度
indegree = defaultdict(int)


# 循环n行,输入n行
for _ in range(n):
    # 在实际考试中,发现必须加入这里的try-except语句才能够满分
    # 题目的输入存在一些未知的错误,不加上只能够通过95%的用例
    try:
        # 输入子节点c,父节点p,子节点最初的收入money
        # c表示children,p表示parent
        c, p, money = map(int, input().split())
        # 在parents邻接表中储存c的父节点为p
        parents[c] = p
        # 在总收入哈希表中初始化c的收入,记录为money
        total_money[c] = money
        # 后续需要进行拓扑排序,父节点p的入度+1
        indegree[p] += 1
    except:
        break


# 寻找boss根节点root,根节点存在以下特征:
# 1. 入度不为0(位于indegree的key中)
# 2. 不是任何一个节点的子节点(不位于parents的key中)
for node in indegree.keys():
    if node not in parents:
        root = node
        break

# 构建队列q维护拓扑排序过程
q = deque()
# 所有入度为0的节点,都是初始的叶节点,存入队列q中
for c in parents.keys():
    if indegree[c] == 0:
        q.append(c)

# 拓扑排序过程
while q:
    # 弹出队头元素,为当前节点c
    c = q.popleft()
    # 如果遍历到根节点root,则直接退出循环
    if c == root:
        break
    # 获得当前节点c的父节点p
    p = parents[c]
    # 父节点的入度+1
    indegree[p] -= 1
    # 弹出的当前节点c的收入已经计算完毕
    # 将其收入整除100后乘15,是提供给父节点p的分销佣金
    # 将该分销佣金加入父节点p的收入中
    total_money[p] += total_money[c] // 100 * 15
    # 若此时父节点的入度为0,则说明其所有子节点均已考虑
    # 该父节点p的总收入计算完毕,将其加入队列
    if indegree[p] == 0:
        q.append(p)

# 输出root的id以及其收入total_money[root]
print(f"{root} {total_money[root]}")

java

import java.util.*;

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();

        // 构建邻接表,key是子节点,value是父节点
        Map<Integer, Integer> parents = new HashMap<>();

        // 构建总收入哈希表,key是节点名,value是该节点的收入
        Map<Integer, Integer> totalMoney = new HashMap<>();

        // 构建入度哈希表,key是节点名,value是入度
        Map<Integer, Integer> indegree = new HashMap<>();

        // 循环n行,输入n行
        for (int i = 0; i < n; i++) {
            try {
                // 输入子节点c,父节点p,子节点最初的收入money
                int c = scanner.nextInt();
                int p = scanner.nextInt();
                int money = scanner.nextInt();

                // 在parents邻接表中储存c的父节点为p
                parents.put(c, p);

                // 在总收入哈希表中初始化c的收入,记录为money
                totalMoney.put(c, money);

                // 后续需要进行拓扑排序,父节点p的入度+1
                indegree.put(p, indegree.getOrDefault(p, 0) + 1);

            } catch (Exception e) {
                break;
            }
        }

        // 寻找boss根节点root,根节点存在以下特征:
        // 1. 入度不为0(位于indegree的key中)
        // 2. 不是任何一个节点的子节点(不位于parents的key中)
        int root = -1;
        for (int node : indegree.keySet()) {
            if (!parents.containsKey(node)) {
                root = node;
                break;
            }
        }

        // 构建队列q维护拓扑排序过程
        Queue<Integer> q = new LinkedList<>();
        // 所有入度为0的节点,都是初始的叶节点,存入队列q中
        for (int c : parents.keySet()) {
            if (indegree.getOrDefault(c, 0) == 0) {
                q.offer(c);
            }
        }

        // 拓扑排序过程
        while (!q.isEmpty()) {
            // 弹出队头元素,为当前节点c
            int c = q.poll();

            // 如果遍历到根节点root,则直接退出循环
            if (c == root) {
                break;
            }

            // 获得当前节点c的父节点p
            int p = parents.get(c);

            // 父节点的入度-1
            indegree.put(p, indegree.get(p) - 1);

            // 弹出的当前节点c的收入已经计算完毕
            // 将其收入整除100后乘15,是提供给父节点p的分销佣金
            // 将该分销佣金加入父节点p的收入中
            totalMoney.put(p, totalMoney.getOrDefault(p, 0) + totalMoney.get(c) / 100 * 15);

            // 若此时父节点的入度为0,则说明其所有子节点均已考虑
            // 该父节点p的总收入计算完毕,将其加入队列
            if (indegree.get(p) == 0) {
                q.offer(p);
            }
        }

        // 输出root的id以及其收入totalMoney.get(root)
        System.out.println(root + " " + totalMoney.get(root));

        scanner.close();
    }
}

cpp

#include 
#include 
#include 

using namespace std;

int main() {
    int n;
    cin >> n;

    // 构建邻接表,key是子节点,value是父节点
    unordered_map<int, int> parents;

    // 构建总收入哈希表,key是节点名,value是该节点的收入
    unordered_map<int, int> total_money;

    // 构建入度哈希表,key是节点名,value是入度
    unordered_map<int, int> indegree;

    // 循环n行,输入n行
    for (int i = 0; i < n; i++) {
        // 在实际考试中,发现必须加入这里的try-catch语句才能够满分
        // 题目的输入存在一些未知的错误,不加上只能够通过95%的用例
        try {
            // 输入子节点c,父节点p,子节点最初的收入money
            int c, p, money;
            cin >> c >> p >> money;

            // 在parents邻接表中储存c的父节点为p
            parents[c] = p;

            // 在总收入哈希表中初始化c的收入,记录为money
            total_money[c] = money;

            // 后续需要进行拓扑排序,父节点p的入度+1
            indegree[p]++;
        } catch (...) {
            break;
        }
    }

    // 寻找boss根节点root,根节点存在以下特征:
    // 1. 入度不为0(位于indegree的key中)
    // 2. 不是任何一个节点的子节点(不位于parents的key中)
    int root = -1;
    for (const auto& pair : indegree) {
        int node = pair.first;
        if (parents.find(node) == parents.end()) {
            root = node;
            break;
        }
    }

    // 构建队列q维护拓扑排序过程
    queue<int> q;
    // 所有入度为0的节点,都是初始的叶节点,存入队列q中
    for (const auto& pair : parents) {
        int c = pair.first;
        if (indegree[c] == 0) {
            q.push(c);
        }
    }

    // 拓扑排序过程
    while (!q.empty()) {
        // 弹出队头元素,为当前节点c
        int c = q.front();
        q.pop();

        if (c == root)
            break;

        // 获得当前节点c的父节点p
        int p = parents[c];

        // 父节点的入度-1
        indegree[p]--;

        // 弹出的当前节点c的收入已经计算完毕
        // 将其收入整除100后乘15,是提供给父节点p的分销佣金
        // 将该分销佣金加入父节点p的收入中
        total_money[p] += total_money[c] / 100 * 15;

        // 若此时父节点的入度为0,则说明其所有子节点均已考虑
        // 该父节点p的总收入计算完毕,将其加入队列
        if (indegree[p] == 0) {
            q.push(p);
        }
    }

    // 输出root的id以及其收入total_money[root]
    cout << root << " " << total_money[root] << endl;

    return 0;
}

时空复杂度

时间复杂度:O(N)。需要遍历每一个节点。

空间复杂度:O(N)。入度哈希表和邻接表所占空间。

*解法二:自底向上的DFS

python

# 题目:【BFS】2024E-BOSS的收入
# 分值:200
# 作者:许老师-闭着眼睛学数理化
# 算法:自底向上DFS
# 代码看不懂的地方,请直接在群上提问

from collections import defaultdict


# 自底向上的dfs函数
# node为当前节点
# neighbor_dic为邻接表
# total_money为每一个节点的总收入
def dfs(node, neighbor_dic, total_money):
    # 如果当前节点node不是一个父节点,则说明其为叶子节点
    # 叶节点的总收入无需修改
    # 直接返回
    if node not in neighbor_dic:
        return
        
    # 考虑当前节点的所有子节点child
    for child in neighbor_dic[node]:
        # 自底向上,先对子节点进行DFS调用,更新子节点的总收入
        dfs(child, neighbor_dic, total_money)
        # 子节点的DFS调用后,子节点的总收入已经计算完毕
        # 将其更新入当前节点中
        total_money[node] += total_money[child] // 100 * 15

    return

# 输入边的个数
n = int(input())

# 构建表示树形结构的邻接表
# 其中key是节点名,value是其所有子节点children构成的列表
neighbor_dic = defaultdict(list)

# 构建总收入哈希表,key是节点名,value是该节点的收入
total_money = defaultdict(int)


# 循环n行,输入n行
for _ in range(n):
    # 在实际考试中,发现必须加入这里的try-except语句才能够满分
    # 题目的输入存在一些未知的错误,不加上只能够通过95%的用例
    try:
        # 输入子节点c,父节点p,子节点最初的收入money
        # c表示children,p表示parent
        c, p, money = map(int, input().split())
        # 在邻接表中储存节点p的子节点包含c
        neighbor_dic[p].append(c)
        # 在总收入哈希表中初始化c的收入,记录为money
        total_money[c] = money
    except:
        break


# 寻找根节点,存在于neighbor_dic中,
# 且未不位于total_money中的节点为根节点
for p in neighbor_dic:
    if p not in total_money:
        root = p
        break

# 递归入口,调用根节点root
dfs(root, neighbor_dic, total_money)

# 输出root的id以及其收入total_money[root]
print(f"{root} {total_money[root]}")

java

import java.util.*;

public class Main {

    // 自底向上的DFS函数
    // node为当前节点
    // neighborDic为邻接表
    // totalMoney为每一个节点的总收入
    public static void dfs(int node, Map<Integer, List<Integer>> neighborDic, Map<Integer, Integer> totalMoney) {
        // 如果当前节点node不是一个父节点,则说明其为叶子节点
        // 叶节点的总收入无需修改,直接返回
        if (!neighborDic.containsKey(node)) {
            return;
        }

        // 考虑当前节点的所有子节点child
        for (int child : neighborDic.get(node)) {
            // 自底向上,先对子节点进行DFS调用,更新子节点的总收入
            dfs(child, neighborDic, totalMoney);
            // 子节点的DFS调用后,子节点的总收入已经计算完毕
            // 将其更新入当前节点中
            totalMoney.put(node, totalMoney.getOrDefault(node, 0) + totalMoney.get(child) / 100 * 15);
        }
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();

        // 构建表示树形结构的邻接表
        Map<Integer, List<Integer>> neighborDic = new HashMap<>();

        // 构建总收入哈希表,key是节点名,value是该节点的收入
        Map<Integer, Integer> totalMoney = new HashMap<>();

        // 循环n行,输入n行
        for (int i = 0; i < n; i++) {
            try {
                // 输入子节点c,父节点p,子节点最初的收入money
                int c = scanner.nextInt();
                int p = scanner.nextInt();
                int money = scanner.nextInt();

                // 在邻接表中储存节点p的子节点包含c
                neighborDic.computeIfAbsent(p, k -> new ArrayList<>()).add(c);

                // 在总收入哈希表中初始化c的收入,记录为money
                totalMoney.put(c, money);
            } catch (Exception e) {
                break;
            }
        }

        // 寻找根节点,存在于neighborDic中,且未不位于totalMoney中的节点为根节点
        int root = -1;
        for (int p : neighborDic.keySet()) {
            if (!totalMoney.containsKey(p)) {
                root = p;
                break;
            }
        }

        // 递归入口,调用根节点root
        dfs(root, neighborDic, totalMoney);

        // 输出root的id以及其收入totalMoney.get(root)
        System.out.println(root + " " + totalMoney.get(root));

        scanner.close();
    }
}

cpp

#include 
#include 
#include 
#include 

using namespace std;

// 自底向上的DFS函数
// node为当前节点
// neighborDic为邻接表
// totalMoney为每一个节点的总收入
void dfs(int node, unordered_map<int, vector<int>>& neighborDic, unordered_map<int, int>& totalMoney) {
    // 如果当前节点node不是一个父节点,则说明其为叶子节点
    // 叶节点的总收入无需修改,直接返回
    if (neighborDic.find(node) == neighborDic.end()) {
        return;
    }

    // 考虑当前节点的所有子节点child
    for (int child : neighborDic[node]) {
        // 自底向上,先对子节点进行DFS调用,更新子节点的总收入
        dfs(child, neighborDic, totalMoney);
        // 子节点的DFS调用后,子节点的总收入已经计算完毕
        // 将其更新入当前节点中
        totalMoney[node] += totalMoney[child] / 100 * 15;
    }
}

int main() {
    int n;
    cin >> n;

    // 构建表示树形结构的邻接表
    unordered_map<int, vector<int>> neighborDic;

    // 构建总收入哈希表,key是节点名,value是该节点的收入
    unordered_map<int, int> totalMoney;

    // 循环n行,输入n行
    for (int i = 0; i < n; i++) {
        try {
            // 输入子节点c,父节点p,子节点最初的收入money
            int c, p, money;
            cin >> c >> p >> money;

            // 在邻接表中储存节点p的子节点包含c
            neighborDic[p].push_back(c);

            // 在总收入哈希表中初始化c的收入,记录为money
            totalMoney[c] = money;
        } catch (...) {
            break;
        }
    }

    // 寻找根节点,存在于neighborDic中,且未不位于totalMoney中的节点为根节点
    int root = -1;
    for (const auto& pair : neighborDic) {
        int p = pair.first;
        if (totalMoney.find(p) == totalMoney.end()) {
            root = p;
            break;
        }
    }

    // 递归入口,调用根节点root
    dfs(root, neighborDic, totalMoney);

    // 输出root的id以及其收入totalMoney[root]
    cout << root << " " << totalMoney[root] << endl;

    return 0;
}

时空复杂度

时间复杂度:O(N)。需要遍历每一个节点。

空间复杂度:O(N)。邻接表所占空间。


华为OD算法/大厂面试高频题算法练习冲刺训练

  • 华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务300+同学成功上岸!

  • 课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化

  • 每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!

  • 60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁

  • 可上全网独家的欧弟OJ系统练习华子OD、大厂真题

  • 可查看链接 大厂真题汇总 & OD真题汇总(持续更新)

  • 绿色聊天软件戳 od1336了解更多

你可能感兴趣的:(#,BFS,#,拓扑排序,最新华为OD真题,算法,java,c++,python,leetcode,华为od,深度优先)