华为二面,轻松拿下!

图片

来源公众号:吴师兄学算法  ID:CXYxiaowu

最近先给大家更新一些华为笔试真题。

题目描述

小慕是一名音乐服务开发者,为了提高用户体验,他需要解决推荐歌单的同质化问题。具体来说,他需要确保推荐给用户的歌单中不包含相同的歌曲。

给定一个包含 N 个歌单和 M 条歌单重复记录的数据集,每个歌单用一个从 1 到 N 的整数编号。每条歌单重复记录包含两个歌单的 ID,表示这两个歌单有相同的歌曲。

小慕的任务是对这些歌单进行合并,找出合并后的最小歌单数量,且合并后的歌单中不能有相同的歌曲。

输入格式

第一行包含两个整数 N 和 M,分别表示歌单的数量和有相同歌曲的歌单记录数。

接下来的 M 行,每行包含两个整数编号 X 和 Y,表示编号为 X 和 Y 的歌单有相同的歌曲。

输入不会出现相同歌单,例如不会出现 "1 1" 这种输入。

输入不会出现多条重复的记录,例如 "1 2" 不会出现多次。

最大歌单数量不超过 100。

歌单有重复歌曲记录数 M 不会超过 1024。

如果歌单 1 和 2 有相同歌曲,歌单 2 和 3 有相同歌曲,歌单 1 和 3 不一定包含相同歌曲。

输出格式

输出一个整数,表示合并后的最小歌单数。

样例

输入样例 1

5 61 21 31 42 32 54 5

输出样例 1

3

提示 1

有 5 个歌单,歌单编码从 1 到 5;有 6 条重复歌单记录,每一条记录包含了歌单的编码。

歌单 1 和 2 有相同歌曲;

歌单 1 和 3 有相同歌曲;

歌单 1 和 4 有相同歌曲;

歌单 2 和 3 有相同歌曲;

歌单 2 和 5 有相同歌曲;

歌单 4 和 5 有相同歌曲。

输出合并后的最小歌单数为 3,合并后的 3 个歌单内没有相同歌曲。例如:

歌单 1 和 5 一组;

歌单 3 和 4 一组;

歌单 2 一组。

或者:

歌单 1 和 5 一组;

歌单 2 和 4 一组;

歌单 3 一组。

合并组合可能有多种,只需要输出合并后的最小数。

输入样例 2

4 31 21 31 4

输出样例 2

2

提示 2

歌单 2、3、4 一组,没有相同歌曲;

歌单 1 一组。

题目解析

对于用例1,其图如下:

由于2的度数最大,因此选择1,染色成0.

剩余的节点,由于2的度数最大,因此选择2,染色成1.

剩余的节点,由于3的度数最大,在此之上,其饱和度目前也是最大,因此选择3,染色成2.

剩余的节点,由于4的度数最大,因此选择4,染色成1.

剩余的节点只剩下5,染色成0.

理解题目

在音乐推荐系统中,同质化的歌单会降低用户体验,因此需要对相似的歌单进行合并,确保最终的歌单列表中没有重复的歌曲。题目要求我们找到合并后的最小歌单数量。给定 N 个歌单以及 M 条重复歌曲记录,每条记录表明两个歌单之间有相同的歌曲,因此它们不能同时出现在同一个合并歌单中。

换句话说,题目可以抽象为一个 图的着色问题。其中:

  • 每个歌单可以看作图中的一个节点。

  • 如果两个歌单有相同的歌曲,则它们之间存在一条无向边。

  • 目标是找到最少的颜色(分组),使得相邻的节点颜色不同,即使得每个独立分组内没有相同歌曲的歌单。

使用算法

这道题的本质是 图的着色问题,目标是求出 图的最小染色数(Chromatic Number)。该题使用了Dsatur(Degree of Saturation)算法来求解最小着色数。DSATUR算法是一种启发式的图着色方法,它通过选择具有最大“饱和度”的节点来优化着色过程。节点的饱和度指的是它已经着色的邻居节点的数量,饱和度较高的节点通常是着色顺序中较为优先的节点。

具体来说,Dsatur的基本思想是:

  • 每次选择当前饱和度最大的节点来着色。

  • 如果有多个节点具有相同的饱和度,则选择度数最大的节点,即与更多节点相连的节点。

  • 这样做的原因是通过优先给“冲突”更多的节点着色,可以有效减少后续分配设备时的冲突。

最终的结果就是最小的合并后歌单数量,即整个图染色所需的最小颜色数。

实现

数据读取与存储

  • 读取 N 和 M,分别代表歌单数量和重复记录数。

  • 使用 邻接表(Adjacency List) 存储歌曲重复关系,即 g[x].append(y) 和 g[y].append(x)。

辅助数据结构

  • colors 数组:记录每个歌单的分组编号,初始值设为 -1(表示未分配)。

  • deg 数组:记录每个歌单的连接度(即有多少个重复关系)。

  • used 数组:用于存储每个歌单的相邻歌单已经使用过的分组编号。

贪心算法

  • get_node():从未分配分组的歌单中选择最优节点,优先选择 已使用分组数较少但连接度高的歌单。

  • get_col(v):获取节点 v 可以使用的最小分组编号(从 0 开始)。

  • 依次为所有歌单分配分组,并确保相邻歌单的分组不同。

最终结果

  • 由于颜色编号从 0 开始,因此最终的最小合并歌单数为 ans + 1。

代码

Python

# 读取歌单数量 N 和 歌单重复记录数 Mn, m = map(int, input().split())# 使用邻接表 g 来存储有相同歌曲的歌单之间的关系g = [[] for _ in range(n)]# 读取 M 条歌单重复记录,并构建邻接表for _ in range(m):    x, y = map(int, input().split())    x -= 1# 将歌单编号从 1 调整到 0,方便数组索引    y -= 1# 同样将编号调整为 0-based    g[x].append(y)  # 记录双向关系    g[y].append(x)# 颜色数组 colors 记录每个歌单被分配到的分组编号,-1 表示尚未分配colors = [-1for _ in range(n)]# 记录每个歌单的连接度(即与多少个歌单存在相同歌曲)deg = [len(g[i]) for i in range(n)]# used 数组存储每个歌单的相邻歌单已经使用过的分组编号used = [set() for _ in range(n)]# 获取一个尚未分配分组的歌单(优先选择已用分组数少且连接度高的)def get_node():    tp = [-1, -1]  # 记录最优候选歌单的 [已用分组数, 连接度]    vertex = -1# 记录符合条件的歌单编号    for v in range(n):        t = [len(used[v]), deg[v]]  # 计算当前歌单的 [已用分组数, 连接度]        if colors[v] == -1and t > tp:  # 选择尚未分配且更优的歌单            tp = t            vertex = v    return vertex# 获取歌单 v 可使用的最小分组编号def get_col(v):    val = 0# 从 0 开始寻找可用的分组编号    while val in used[v]:  # 如果该编号已被相邻歌单使用,则尝试下一个        val += 1    return val# 记录最终需要的最小分组数量ans = -1# 依次为所有歌单分配分组for _ in range(n):    u = get_node()  # 选取一个尚未分配分组的歌单    if u == -1:  # 如果所有歌单都已分配,则退出循环        break    col = get_col(u)  # 获取当前歌单可用的最小分组编号    colors[u] = col  # 为该歌单分配分组    ans = max(ans, col)  # 记录所需的最大分组编号        # 遍历当前歌单的所有相邻歌单,更新它们的已使用分组集合    for v in g[u]:        if colors[v] == -1:  # 如果该相邻歌单尚未分配分组            used[v].add(col)  # 标记该分组已经被使用# 由于分组编号是从 0 开始的,所以最终答案需要加 1print(ans + 1)

C++

#include <iostream>#include <vector>#include <set>#include <algorithm>usingnamespacestd;// 全局变量定义int n, m;  // 歌单数量 n 和 歌单重复记录数 mvector<vector<int>> g;  // 邻接表 g,用于记录哪些歌单有相同歌曲vector<int> colors;  // 记录每个歌单的分组编号,-1 表示未分组vector<int> deg;  // 记录每个歌单的连接度(即它与多少个歌单有相同歌曲)vector<set<int>> used;  // 记录每个歌单的邻接歌单已使用的分组编号// 获取一个尚未分组的歌单编号int get_node() {    vector<int> tp = {-1, -1};  // 记录当前候选歌单的度数和已用分组数    int vertex = -1;  // 记录符合条件的歌单编号        for (int v = 0; v < n; v++) {        vector<int> t = {used[v].size(), deg[v]};        if (colors[v] == -1 && t > tp) {  // 选择一个邻接歌单多、已用分组少的歌单            tp = t;            vertex = v;        }    }    return vertex;}// 获取歌单 v 可以使用的最小分组编号int get_col(int v) {    int val = 0;    while (used[v].count(val)) {  // 如果该编号已被邻接歌单使用,则尝试下一个编号        val++;    }    return val;}int main() {    cin >> n >> m;  // 读取歌单数量和重复记录数    g.resize(n);  // 初始化邻接表        // 读取重复记录,并建立邻接表    for (int i = 0; i < m; i++) {        int x, y;        cin >> x >> y;        x--, y--;  // 歌单编号调整为 0 开始        g[x].push_back(y);        g[y].push_back(x);    }        colors.resize(n, -1);  // 初始化分组编号数组    deg.resize(n);  // 初始化连接度数组    used.resize(n);  // 初始化已用分组记录        for (int i = 0; i < n; i++) {        deg[i] = g[i].size();  // 计算每个歌单的连接度    }        int ans = -1;  // 记录最大分组编号        // 进行分组    for (int i = 0; i < n; i++) {        int u = get_node();  // 获取一个未分组的歌单        if (u == -1) break;                int col = get_col(u);  // 获取可以使用的最小分组编号        colors[u] = col;        ans = max(ans, col);  // 记录最大分组编号                // 将该歌单的分组编号记录到其邻接歌单的已使用分组集合中        for (int v : g[u]) {            if (colors[v] == -1) {                used[v].insert(col);            }        }    }        cout << ans + 1 << endl;  // 输出最小合并歌单数量    return0;}

Java

import java.util.*;publicclass Main {    // 全局变量定义    staticint n, m;  // 歌单数量 n 和 歌单重复记录数 m    static ArrayList<Integer>[] g;  // 邻接表 g,记录哪些歌单有相同歌曲    staticint[] colors;  // 记录每个歌单的分组编号,-1 表示未分组    staticint[] deg;  // 记录每个歌单的连接度    static HashSet<Integer>[] used;  // 记录每个歌单的邻接歌单已使用的分组编号        // 获取一个尚未分组的歌单编号    static int getNode() {        int[] tp = {-1, -1};  // 记录当前候选歌单的度数和已用分组数        int vertex = -1;                for (int v = 0; v < n; v++) {            int[] t = {used[v].size(), deg[v]};            if (colors[v] == -1 && compareArrays(t, tp) > 0) {  // 选择邻接歌单多、已用分组少的歌单                tp = t;                vertex = v;            }        }        return vertex;    }        // 获取歌单 v 可以使用的最小分组编号    static int getCol(int v) {        int val = 0;        while (used[v].contains(val)) {            val++;  // 如果该编号已被邻接歌单使用,则尝试下一个编号        }        return val;    }    // 比较两个数组的大小    static int compareArrays(int[] t, int[] tp) {        for (int i = 0; i < t.length; i++) {            if (t[i] > tp[i]) return1;            if (t[i] < tp[i]) return -1;        }        return0;    }    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        n = sc.nextInt();  // 读取歌单数量        m = sc.nextInt();  // 读取重复记录数                g = new ArrayList[n];        for (int i = 0; i < n; i++) {            g[i] = new ArrayList<>();        }                for (int i = 0; i < m; i++) {            int x = sc.nextInt() - 1;            int y = sc.nextInt() - 1;            g[x].add(y);            g[y].add(x);        }                colors = newint[n];        Arrays.fill(colors, -1);        deg = newint[n];        used = new HashSet[n];        for (int i = 0; i < n; i++) {            deg[i] = g[i].size();            used[i] = new HashSet<>();        }                int ans = -1;                for (int i = 0; i < n; i++) {            int u = getNode();  // 获取一个未分组的歌单            if (u == -1) break;                        int col = getCol(u);  // 获取可以使用的最小分组编号            colors[u] = col;            ans = Math.max(ans, col);                        for (int v : g[u]) {                if (colors[v] == -1) {                    used[v].add(col);                }            }        }                System.out.println(ans + 1);  // 输出最小合并歌单数量        sc.close();    }}

时空复杂度

时间复杂度:O(n^2)。

空间复杂度:O(n)。

相关题目

886. 可能的二分法 - 力扣(LeetCode)

图片

  推荐阅读:

  • LeetCode 中等水平是什么?

  • 字母异位词分组 | LeetCode中等算法题

  • 有人 LeetCode 第一题都做不出来!

  • 微软二面,排序链表(LeetCode 148)

  • 想刷 LeetCode ,在此之前需要做什么准备?

  • (0)
    wd123_cnwd123_cn
    上一篇 2025年3月8日 下午2:29
    下一篇 2025年3月8日 下午2:29

    相关文章

    • 小红书笔记老是违规,你可能犯了这72个违规词,直接用完美替代词!

      点击关注 ▲ 艾奇SEM 知识 | 经验 | 资讯 | 资料 四大版块 专注广告投放,一起学会广告营销 你发的小红书笔记是不是老是莫名就违规了? 极有可能是你触犯了这  72个违规词 。 我整理好了,你可直接替代。 看大图: 最后,你还有什么想要的: 欢迎直接告诉我~  

      2025年3月29日
    • 波音正寻求撤回空难相关认罪协议,希望美新一届政府减轻处罚

      来源 | 央视新闻客户端 据美国媒体当地时间24日报道,知情人士透露,美国波音公司正在寻求撤回此前就两起波音737 MAX机型空难事故达成的相关认罪协议。波音希望在新一届政府领导下,美国司法部能减轻处罚。 2018年和2019年,波音737 MAX 8型客机接连发生两起严重空难。2021年美国司法部对波音公司提起相关刑事诉讼,双方达成为期三年的延期…

      2025年3月26日
    • 佛罗里达海滩惊魂:男子日光浴遭吉普车碾压,身受重伤

      原本惬意悠闲的佛罗里达海滩日光浴,却变成了一场噩梦般的遭遇。近日,一名男子在佛罗里达州奥蒙德海滩(Ormond Beach)享受阳光时,不幸被一辆吉普车碾压,身受重伤。 事故经过: 根据沃卢西亚郡(Volusia County)警长办公室在Facebook上发布的消息,4月5日(星期六),这位来自佛罗里达州奥卡拉(Ocala)的33岁男子,当时正脸朝下躺在奥…

      2025年4月10日
    • 马丁·肖特模仿“无礼”采访者吓到伊迪·法尔科:被“嘘”是她童年阴影

      近日,喜剧演员马丁·肖特回忆了一次他扮演吉米尼·格利克(Jiminy Glick)时,让女演员伊迪·法尔科(Edie Falco)“吓了一跳”的经历。原来,肖特在模仿格利克采访法尔科时,因为法尔科打断了他的提问,便对她说了“嘘”。 “嘘”声勾起童年阴影 肖特回忆说:“事后她说她真的被吓到了,因为小时候被‘嘘’是她的阿喀琉斯之踵。所以你可以在她当时的反应中看到…

      2025年3月21日
    • 全球女性运动员联盟成立,旨在提升女性运动表现与健康

      澳大利亚体育委员会(ASC)于周五宣布,来自澳大利亚、美国、英国和新西兰的体育科学家们已经组建了一个名为“全球女性运动员联盟”(GAFA)的合作伙伴关系,旨在通过提供研究和最佳实践信息,帮助女性运动员充分发挥其潜力。 全球女性运动员联盟的使命 ASC表示,GAFA将为运动员、教练和支持人员免费提供“世界领先的证据、表现见解和最佳实践信息”。这一合作伙伴关系的…

      2025年3月7日
    • 全球最大迪士尼乐园,突发火灾

      来源 | 央视新闻、cctv4 当地时间3月22日下午,美国佛罗里达州奥兰多迪士尼乐园“艾波卡特”园区的法国馆发生火灾。迪士尼证实,火灾是在步入式冷却器着火后开始的。目前火势已被扑灭,没有人员伤亡。 火灾发生时,该地区人员已经撤离。 视频截图 据悉,奥兰多迪士尼乐园度假区于1971年在佛罗里达开业,是世界上最大的迪士尼度假区。 转载:潇湘晨报公众号

      2025年3月24日