最近在准备算法面试时遇到一个有趣的师徒排名问题,题目要求我们统计每个师傅手下有多少徒弟的排名超过了自己。这类关系型数据处理在实际开发中很常见,比如社交网络中的影响力分析、企业组织架构中的汇报关系等。
问题的核心在于:给定一组师徒关系对[[师傅排名, 徒弟排名],...],需要计算每个师傅对应的"比自己强的徒弟"数量。这里的"强"指的是排名更高(数字更小)。例如输入[[2,1],[3,2]],输出应该是[0,1,2],表示:
我们需要将输入的师徒关系对转换为更易处理的数据结构。最直观的方式是使用邻接表(Adjacency List)来表示这个有向图:
python复制{
1: [4, 3], # 排名1的师傅有排名4和3的徒弟
2: [4, 1], # 排名2的师傅有排名4和1的徒弟
3: [2], # 排名3的师傅有排名2的徒弟
4: [] # 排名4的师傅没有徒弟
}
这种表示方法有几个优点:
在实际场景中,可能会出现循环依赖(A是B的师傅,B又是A的师傅)。我们需要在算法中检测并处理这种情况:
python复制if c == src: # 发现循环依赖
return 0 # 中断当前统计分支
这种处理方式确保了算法不会陷入无限递归,同时也符合业务逻辑——循环依赖在师徒关系中是不合理的。
算法的核心思想是通过深度优先搜索(DFS)遍历师徒关系图:
highC来存储比当前师傅强的徒弟highChighC的大小python复制def getHighC(fa, f, src, highC):
if f == 1: # 排名第1的师傅不可能有更强的徒弟
return 0
for c in fa[f]:
flag = True
if c < src: # 发现更强的徒弟
if c not in highC:
highC.add(c)
else: # 避免重复统计
flag = False
elif c == src: # 循环依赖
return 0
if flag:
getHighC(fa, c, src, highC)
return len(highC)
在极端情况下(如深度很大的师徒链),直接递归可能导致性能问题。我们通过两种优化提升效率:
highC记录已统计的徒弟,避免重复处理提示:在实际面试中,能够指出这些优化点会大大加分。即使时间有限不能完整实现,也应该说明优化思路。
Python版本充分利用了字典和集合的特性,代码简洁:
python复制fa = {}
for f, c in relations:
fa.setdefault(f, []).append(c)
fa.setdefault(c, []) # 确保所有师傅都有记录
特点:
setdefault简化字典初始化Java版本需要更多样板代码,但类型安全:
java复制HashMap<Integer, ArrayList<Integer>> fa = new HashMap<>();
for (Integer[] relation : relations) {
fa.putIfAbsent(relation[0], new ArrayList<>());
fa.putIfAbsent(relation[1], new ArrayList<>());
fa.get(relation[0]).add(relation[1]);
}
关键点:
putIfAbsent初始化列表JS版本适合前端场景:
javascript复制const fa = {};
for (let relation of relations) {
const [f, c] = relation;
fa[f] ? fa[f].push(c) : (fa[f] = [c]);
fa[c] ? null : (fa[c] = []);
}
特点:
python复制输入: [[2,1],[3,2]]
预期输出: [0,1,2]
这个简单用例验证:
python复制输入: [[1,4],[1,3],[2,4],[2,1],[3,2],[4,5],[5,3]]
预期输出: [0,1,2,1,0]
这个用例验证:
python复制输入: []
预期输出: []
输入: [[1,2]]
预期输出: [0,0]
验证:
在最初的实现中,可能会重复统计间接关系。例如:
code复制A → B → C
A → C
如果不做去重,C会被统计两次。解决方案是使用集合而不是列表来存储结果。
深度递归可能导致栈溢出,特别是当师徒关系形成长链时。虽然题目中的排名数字通常不会太大,但在实际工程中可以考虑:
对于大规模数据(如数万条关系),可以考虑:
这个算法可以应用于多种实际场景:
例如在电商平台中,可以用类似算法分析:
设n为师傅数量,m为关系数量:
时间复杂度:
空间复杂度:
在实际应用中,师徒关系通常是树状或稀疏图,平均性能会比最坏情况好很多。
python复制def getHighC(fa, f, src, highC):
"""
统计比指定师傅强的徒弟数量
:param fa: 师徒关系邻接表
:param f: 当前处理的师傅
:param src: 原始师傅(用于递归)
:param highC: 已发现的强徒弟集合
:return: 强徒弟数量
"""
teacher_to_students比fa更直观建立完善的测试套件:
python复制import unittest
class TestTeacherRank(unittest.TestCase):
def test_basic_case(self):
self.assertEqual(getResult([[2,1],[3,2]]), [0,1,2])
def test_complex_case(self):
self.assertEqual(getResult([[1,4],[1,3],[2,4],[3,2]]), [0,1,2,0])
在实现这个算法的过程中,有几个关键点值得注意:
我在实际编码时最初忽略了循环依赖的情况,导致在某些测试用例下出现无限递归。通过添加c == src的检查解决了这个问题。这也提醒我们,在处理图算法时,必须考虑环路存在的可能性。
对于算法面试,建议按照以下步骤进行分析:
这个题目很好地考察了对图算法的理解和递归思想的运用,是准备技术面试的优秀练习题。