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

42 篇文章 1 订阅
41 篇文章 2 订阅

从2024年4月15号开始,OD机考全部配置为2024D卷
注意两个关键点:

  1. 会遇到C卷复用题。虽然可能存在幸存者偏差,但肯定还会有一大部分的旧题。
  2. 现在又支持做完题目之后倒回去改了。就是可以先做200的再做100的,然后可以反复提交。

在这里插入图片描述

有LeetCode算法/华为OD考试扣扣交流群可加 948025485
可上全网独家的 欧弟OJ系统 练习华子OD、大厂真题
绿色聊天软件戳 od1336了解算法冲刺训练

题目描述与示例

题目描述

在一棵树中,每个节点代表一个家庭成员,节点的数字表示其个人的财富值,一个节点及其直接相连的子节点被定义为一个小家庭

现给你一棵树,请计算出最富裕的小家庭的财富和。

输入描述

第一行为一个数N,表示成员总数,成员编号1-N1<=N<=1000

第二行为N个空格分隔的数,表示编号1-N的成员的财富值,0<=财富值<=1000000

接下来N-1行,每行两个空格分隔的整数(N1,N2),表示N1N2的父节点。

输出描述

最富裕的小家庭的财富和

示例

输入

4
100 200 300 500
1 2
1 3
2 4

输出

700

说明

在这里插入图片描述

所构建出的树如上图所示,其中最小富裕家庭为节点2和节点4构成的小家庭。

解题思路

常规搜索解法

题目所给的数据形式,非常容易想到转化为邻接表来储存数据。

另外,由于数据所给的编号是从1开始的,为了方便后续对应上财富列表的索引(从0开始),可以把-1之后的结果作为编号来储存。

neighbor_dic = defaultdict(list)
for _ in range(n-1):
    fa, ch = map(int, input().split())
    neighbor_dic[fa-1].append(ch-1)

另外,由于本题的输入没有告知根节点root,我们需要根据输入的n-1条边来找到根节点。

根节点root存在特点:根节点root不是任何一个节点的子节点。因此,我们可以根据n-1条边中的所有子节点,反推出唯一那个不是子节点的节点,即为根节点。具体代码如下

neighbor_dic = defaultdict(list)
children_set = set()
for _ in range(n-1):
    fa, ch = map(int, input().split())
    neighbor_dic[fa-1].append(ch-1)
    children_set.add(ch-1)

for i in range(n):
    if i not in children_set:
        root = i
        break

剩下的就是常规的DFS和BFS过程了。由于本题所给数据是树型结构(有向无环图),不会出现同一个节点多次进入的情况,因此无需额外使用check_list来维护节点重复进入。

对于DFS而言,核心函数为

def dfs(neighbor_dic, money_list, i):
    global ans
    little_family_sum = money_list[i]

    for child in neighbor_dic[i]:
        dfs(neighbor_dic, money_list, child)
        little_family_sum += money_list[child]

    ans = max(ans, little_family_sum)

对于BFS而言,核心过程为

q = deque([root])

ans = 0
while q:
    node = q.popleft()
    little_family_sum = money_list[node]
    
    for child in neighbor_dic[node]:
        q.append(child)
        little_family_sum += money_list[child]
        
    ans = max(ans, little_family_sum)

可以发现都涉及相似的过程:

  1. 考虑当前小家庭的父节点i
  2. 初始化当前小家庭的总财富值little_family_sum = money_list[i]
  3. 考虑i的所有子节点child
    1. child进行递归(DFS)/将child加入队列中(BFS)
    2. 根据子节点的财富money_list[ch],更新当前小家庭的总财富值little_family_sum
  4. 基于当前小家庭的总财富值little_family_sum,更新全局的答案ans

无需搜索的简单解法

另外,本题还存在一种非常简单的解法,无需进行DFS/BFS搜索。

由于每一个小家庭仅由父节点和其子节点构成,这里的对应关系可以从边的对应关系中直接得到

在拿到一条边(fa, ch)的时候,实际上我们可以马上知道父节点fa和子节点ch对应的财富money_list[fa]money_list[ch]

仅需额外构建一个列表little_family_listlittle_family_list[i]表示以i为父节点的小家庭的财富。

初始化litte_family_list[i] = money_list[i](这样才能避免父节点的重复计算),然后对于所有的边(fa, ch),将fa的孩子ch的财富加入到这个小家庭中(只往小家庭中加入子节点),即litte_family_list[fa] += money_list[ch]

最后找到litte_family_list中的最大值即为答案。整体的核心代码如下

little_family_list = money_list.copy()
for _ in range(n-1):
    fa, ch = map(int, input().split())
    little_family_list[fa-1] += money_list[ch-1]

print(max(little_family_list))

代码

解法一:DFS

python

# 题目:2023C-寻找最富裕的小家庭
# 分值:100
# 作者:闭着眼睛学数理化
# 算法:DFS
# 代码看不懂的地方,请直接在群上提问


# DFS函数
def dfs(neighbor_dic, money_list, i):
    global ans
    # 初始化当前小家庭的财富值为money_list[i]
    little_family_sum = money_list[i]

    # 考虑所有节点i的子节点child,做两件事情:
    # 1. 对child进行递归
    # 2. 更新little_family_sum的值
    for child in neighbor_dic[i]:
        dfs(neighbor_dic, money_list, child)
        little_family_sum += money_list[child]

    # 更新ans
    ans = max(ans, little_family_sum)

from collections import defaultdict

# 输入节点个数
n = int(input())
# 输入各个节点对应的财富
money_list = list(map(int, input().split()))
# 初始化邻接表
neighbor_dic = defaultdict(list)
# 储存子节点的集合,用来找根节点
children_set = set()

# 建树,输入n-1行
for _ in range(n-1):
    # 输入某一条边对应的父节点和子节点
    # 注意编号是从1开始的,为了方便使用,可以令所有的编号-1
    # 使得编号从0开始
    fa, ch = map(int, input().split())
    # 父节点的邻接表延长
    neighbor_dic[fa-1].append(ch-1)
    # ch是fa的子节点,必然不是根节点,存入children_set中
    children_set.add(ch-1)

# 寻找整棵树的根节点,唯一一个不位于children_set中的节点即为根节点
for i in range(n):
    if i not in children_set:
        root = i
        break

# 初始化ans为0
ans = 0
# 递归调用入口
dfs(neighbor_dic, money_list, root)
print(ans)

java

import java.util.*;

public class Main {
    static int ans = 0;

    static void dfs(HashMap<Integer, List<Integer>> neighborDic, List<Integer> moneyList, int i) {
        // 初始化当前小家庭的财富值为moneyList.get(i)
        int littleFamilySum = moneyList.get(i);

        // 考虑所有节点i的子节点child,做两件事情:
        // 1. 对child进行递归
        // 2. 更新littleFamilySum的值
        List<Integer> children = neighborDic.get(i);
        if (children != null) {
            for (int child : children) {
                dfs(neighborDic, moneyList, child);
                littleFamilySum += moneyList.get(child);
            }
        }

        // 更新ans
        ans = Math.max(ans, littleFamilySum);
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        List<Integer> moneyList = new ArrayList<>();
        HashMap<Integer, List<Integer>> neighborDic = new HashMap<>();
        HashSet<Integer> childrenSet = new HashSet<>();

        for (int i = 0; i < n; i++) {
            moneyList.add(scanner.nextInt());
        }

        for (int i = 0; i < n - 1; i++) {
            int fa = scanner.nextInt();
            int ch = scanner.nextInt();
            if (!neighborDic.containsKey(fa - 1)) {
                neighborDic.put(fa - 1, new ArrayList<>());
            }
            neighborDic.get(fa - 1).add(ch - 1);
            childrenSet.add(ch - 1);
        }

        int root = -1;
        for (int i = 0; i < n; i++) {
            if (!childrenSet.contains(i)) {
                root = i;
                break;
            }
        }

        dfs(neighborDic, moneyList, root);
        System.out.println(ans);
    }
}

cpp

#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>

using namespace std;

int ans = 0;

void dfs(unordered_map<int, vector<int>>& neighborDic, vector<int>& moneyList, int i) {
    // 初始化当前小家庭的财富值为moneyList[i]
    int littleFamilySum = moneyList[i];

    // 考虑所有节点i的子节点child,做两件事情:
    // 1. 对child进行递归
    // 2. 更新littleFamilySum的值
    for (int child : neighborDic[i]) {
        dfs(neighborDic, moneyList, child);
        littleFamilySum += moneyList[child];
    }

    // 更新ans
    ans = max(ans, littleFamilySum);
}

int main() {
    int n;
    cin >> n;
    vector<int> moneyList(n);
    unordered_map<int, vector<int>> neighborDic;
    unordered_set<int> childrenSet;

    for (int i = 0; i < n; i++) {
        cin >> moneyList[i];
    }

    for (int i = 0; i < n - 1; i++) {
        int fa, ch;
        cin >> fa >> ch;
        neighborDic[fa - 1].push_back(ch - 1);
        childrenSet.insert(ch - 1);
    }

    int root = -1;
    for (int i = 0; i < n; i++) {
        if (childrenSet.find(i) == childrenSet.end()) {
            root = i;
            break;
        }
    }

    dfs(neighborDic, moneyList, root);
    cout << ans << endl;

    return 0;
}

时空复杂度

时间复杂度:O(N)。需要遍历整棵树。

空间复杂度:O(N),递归编译栈所占空间。

解法二:BFS

python

# 题目:2023C-寻找最富裕的小家庭
# 分值:100
# 作者:闭着眼睛学数理化
# 算法:BFS
# 代码看不懂的地方,请直接在群上提问

from collections import defaultdict, deque

# 输入节点个数
n = int(input())
# 输入各个节点对应的财富
money_list = list(map(int, input().split()))
# 初始化邻接表
neighbor_dic = defaultdict(list)
# 储存子节点的集合,用来找根节点
children_set = set()

# 建树,输入n-1行
for _ in range(n-1):
    # 输入某一条边对应的父节点和子节点
    # 注意编号是从1开始的,为了方便使用,可以令所有的编号-1
    # 使得编号从0开始
    fa, ch = map(int, input().split())
    # 父节点的邻接表延长
    neighbor_dic[fa-1].append(ch-1)
    # ch是fa的子节点,必然不是根节点,存入children_set中
    children_set.add(ch-1)

# 寻找整棵树的根节点,唯一一个不位于children_set中的节点即为根节点
for i in range(n):
    if i not in children_set:
        root = i
        break


# 初始化队列,包含根节点root的编号
q = deque([root])

ans = 0
# 进行BFS搜索
while q:
    # 弹出队头节点,为当前小家庭的父节点
    node = q.popleft()
    # 初始化当前小家庭的财富值为money_list[node]
    little_family_sum = money_list[node]
    # 遍历node的所有子节点child,做两件事情:
    # 1. 令child入队
    # 2. 更新little_family_sum的值
    for child in neighbor_dic[node]:
        q.append(child)
        little_family_sum += money_list[child]
    # 更新ans
    ans = max(ans, little_family_sum)

print(ans)

java

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] moneyList = new int[n];
        Map<Integer, List<Integer>> neighborDic = new HashMap<>();
        Set<Integer> childrenSet = new HashSet<>();

        for (int i = 0; i < n; i++) {
            moneyList[i] = scanner.nextInt();
            neighborDic.put(i, new ArrayList<>());
        }

        for (int i = 0; i < n - 1; i++) {
            int fa = scanner.nextInt() - 1;
            int ch = scanner.nextInt() - 1;
            neighborDic.get(fa).add(ch);
            childrenSet.add(ch);
        }

        int root = 0;
        for (int i = 0; i < n; i++) {
            if (!childrenSet.contains(i)) {
                root = i;
                break;
            }
        }

        Queue<Integer> queue = new LinkedList<>();
        queue.offer(root);

        int ans = 0;
        while (!queue.isEmpty()) {
            int node = queue.poll();
            int littleFamilySum = moneyList[node];
            for (int child : neighborDic.get(node)) {
                queue.offer(child);
                littleFamilySum += moneyList[child];
            }
            ans = Math.max(ans, littleFamilySum);
        }

        System.out.println(ans);
    }
}

cpp

#include <iostream>
#include <vector>
#include <unordered_map>
#include <queue>
#include <set>

using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> moneyList(n);
    unordered_map<int, vector<int>> neighborDic;
    set<int> childrenSet;

    for (int i = 0; i < n; ++i) {
        cin >> moneyList[i];
        neighborDic[i] = vector<int>();
    }

    for (int i = 0; i < n - 1; ++i) {
        int fa, ch;
        cin >> fa >> ch;
        neighborDic[fa - 1].push_back(ch - 1);
        childrenSet.insert(ch - 1);
    }

    int root = 0;
    for (int i = 0; i < n; ++i) {
        if (childrenSet.find(i) == childrenSet.end()) {
            root = i;
            break;
        }
    }

    queue<int> q;
    q.push(root);

    int ans = 0;
    while (!q.empty()) {
        int node = q.front();
        q.pop();
        int littleFamilySum = moneyList[node];
        for (int child : neighborDic[node]) {
            q.push(child);
            littleFamilySum += moneyList[child];
        }
        ans = max(ans, littleFamilySum);
    }

    cout << ans << endl;

    return 0;
}

时空复杂度

时间复杂度:O(N)。需要遍历整棵树。

空间复杂度:O(N)q最大所占空间。

解法三:直接模拟(最简单)

python

# 题目:2023C-寻找最富裕的小家庭
# 分值:100
# 作者:闭着眼睛学数理化
# 算法:直接模拟
# 代码看不懂的地方,请直接在群上提问


# 输入节点个数
n = int(input())
# 输入各个节点对应的财富
money_list = list(map(int, input().split()))
# 构建小家庭财富列表,初始化为每一个节点i的财富
# little_family_list[i]表示以节点i为父节点的小家庭的财富
little_family_list = money_list.copy()

# 输入n-1行
for _ in range(n-1):
    # 输入某一条边对应的父节点和子节点
    # 注意编号是从1开始的,为了方便使用,可以令所有的编号-1
    # 使得编号从0开始
    fa, ch = map(int, input().split())
    little_family_list[fa-1] += money_list[ch-1]

# 取little_family_list中的最大值即可
print(max(little_family_list))

java

import java.util.Scanner;

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

        for (int i = 0; i < n; i++) {
            moneyList[i] = scanner.nextInt();
            littleFamilyList[i] = moneyList[i];
        }

        for (int i = 0; i < n - 1; i++) {
            int fa = scanner.nextInt();
            int ch = scanner.nextInt();
            littleFamilyList[fa - 1] += moneyList[ch - 1];
        }

        int maxWealth = Integer.MIN_VALUE;
        for (int wealth : littleFamilyList) {
            maxWealth = Math.max(maxWealth, wealth);
        }

        System.out.println(maxWealth);
    }
}

JavaScript

const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
 
void (async function () {
  const n = parseInt(await readline());
  const moneyList = (await readline()).split(" ").map(Number);
  const littleFamilyList = [...moneyList];
 
  for (let i = 0; i < n - 1; i++) {
    const [fa, ch] = (await readline()).split(" ").map(Number);
    littleFamilyList[fa-1] += moneyList[ch-1];
  }
 
  console.log(Math.max(...littleFamilyList));
})();

cpp

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> moneyList(n);
    vector<int> littleFamilyList(n);

    for (int i = 0; i < n; i++) {
        cin >> moneyList[i];
        littleFamilyList[i] = moneyList[i];
    }

    for (int i = 0; i < n - 1; i++) {
        int fa, ch;
        cin >> fa >> ch;
        littleFamilyList[fa - 1] += moneyList[ch - 1];
    }

    int maxWealth = *max_element(littleFamilyList.begin(), littleFamilyList.end());
    cout << maxWealth << endl;

    return 0;
}

时空复杂度

时间复杂度:O(N)。需要遍历所有边。

空间复杂度:O(N)little_family_list所占空间。


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

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

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

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

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

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

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

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

  • 25
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
题目描述 给定一个无向图,把图的结点分成两组,要求相同组内的结点之间没有连边,求这样分组的可能方案数。 输入格式 第一行一个整数 n,表示图的结点数。 接下来有 n 行,其中第 i 行的第 j 个整数表示结点 i 和结点 j 之间是否有连边。 输出格式 输出一个整数,为方案数。 输入样例 4 0 1 1 1 1 0 1 0 1 1 0 1 1 0 1 0 输出样例 10 解题思路 本题解法很多,以下介绍两种较常见的做法。 做法一:二分图染色 将整个无向图按照二分图划分为两部分,其中每一部分内的节点都没有互相连通的边。即,将图中的每个节点分为两组,使得每组内没有连边,此时的分组情况可以确定,方案数为 2^(n/2)。这里可以使用 DFSBFS 实现,需要注意以下两个细节: - 图不一定联通,因此需要对所有的节点进行遍历; - 一个无向图不一定是二分图,因此需要处理在原图中连通的节点被划分在同一组的情形。 代码如下: Java版: import java.util.*; public class Main { static int n; static int[][] w; static int[] color; public static void main(String[] args) { Scanner sc = new Scanner(System.in); n = sc.nextInt(); w = new int[n][n]; color = new int[n]; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) w[i][j] = sc.nextInt(); dfs(0, 1); int ans = 1; for (int c : color) { if (c == 0) ans *= 2; } System.out.println(ans); } static void dfs(int u, int c) { color[u] = c; for (int v = 0; v < n; v++) { if (w[u][v] == 0) continue; if (color[v] == 0) { dfs(v, 3 - c); // 如果是颜色1,则下一次染成颜色2;如果是颜色2,下一次染成颜色1 } else if (color[v] == c) { System.out.println(0); // 相邻节点颜色相同,说明不是二分图 System.exit(0); // 必须结束程序 } } } } Python版: n = int(input()) w = [list(map(int, input().split())) for _ in range(n)] color = [0] * n # color 记录染色信息,初始值为0 def dfs(u, c): color[u] = c for v in range(n): if w[u][v] == 0: continue if color[v] == 0: dfs(v, 3 - c) elif color[v] == c: print(0) exit() dfs(0, 1) ans = 1 for c in color: if c == 0: ans *= 2 print(ans) 做法二:矩阵树定理 矩阵树定理可以用于计算无向图的生成树个数以及最小割的计算。这里只介绍如何使用矩阵树定理计算无向图的染色数。 定义行列式的值为图的邻接矩阵去掉一行一列之后的行列式的值,即 d = |(W)ij|,其中 W 表示邻接矩阵。则生成树个数等于 d^(n-2)。 如果将邻接矩阵 W 的每一行都减去该行的最后一个元素,并将对角线顶点连接起来,形成的新矩阵可以用于计算染色数。新矩阵记作 M,其中 M 的 i 行表示第 i 个结点都和哪些结点相连,例如 M 的第 i 行为 [0, 1, 1, 1],表示 1、2、3 这三个结点和 i 相连。 将 M 的第 i 行中的每个元素变成该行所有数的相反数,再将对角线顶点连接起来,形成的新矩阵记作 K,那么 K 的行列式值即为答案。例如,如果 M 的第 i 行为 [0, 1, 1, 1],那么 K 矩阵的该行为 [-3, 1, 1, 1]。 代码如下: Java版: import java.util.*; public class Main { static int n; static int[][] w; static int[][] m; public static void main(String[] args) { Scanner sc = new Scanner(System.in); n = sc.nextInt(); w = new int[n][n]; m = new int[n - 1][n - 1]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { w[i][j] = sc.nextInt(); if (i != j && w[i][j] != 0) { m[Math.min(i, j)][Math.max(i, j)] = -1; } } } for (int i = 0; i < n - 1; i++) m[i][i] -= Arrays.stream(m[i]).sum(); System.out.println(det()); } static long det() { long ans = 1; for (int i = 0; i < n - 2; i++) { for (int j = i + 1; j < n - 1; j++) { while (m[j][i] != 0) { long t = m[i][i] / m[j][i]; for (int k = i; k < n - 1; k++) m[i][k] -= t * m[j][k]; for (int k = i; k < n - 1; k++) m[i][k] ^= m[j][k] ^= m[i][k] ^= m[j][k]; ans = -ans; } } ans *= m[i][i]; } return ans; } } Python版: n = int(input()) w = [list(map(int, input().split())) for _ in range(n)] m = [[0] * (n - 1) for _ in range(n - 1)] for i in range(n): for j in range(i + 1, n): if w[i][j] == 1: m[min(i, j)][max(i, j) - 1] = -1 for i in range(n - 1): m[i][i] = sum(m[i]) - m[i][i] def det(): ans = 1 for i in range(n - 2): for j in range(i + 1, n - 1): while m[j][i]: t = m[i][i] // m[j][i] for k in range(i, n - 1): m[i][k] -= t * m[j][k] m[i], m[j] = m[j], m[i] ans = -ans ans *= m[i][i] return ans print(det())

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值