销售层级人数统计

64 阅读8分钟

销售层级人数统计

问题背景

某部门的销售负责人按严格的层级树进行管理。每个级别的负责人有且仅有一位直接上级。层级的顶端是销售总裁,其编号固定为 1

例如,销售总裁(第1层)直接管理多个国家的销售负责人(第2层),每个国家负责人再管理其下属的大区负责人(第3层),以此类推,形成一个树状的汇报结构。

核心定义

  1. 层级关系 (Hierarchy):

    整个部门的组织架构可以看作一棵树,节点是销售负责人,边代表直接的上下级汇报关系。

  2. 管辖范围 (Subordinate Tree):

    对于任意指定的负责人,其“名下”或“管辖范围”包括其自身以及所有直接或间接向他汇报的下属。这同样构成一棵以该负责人为根的子树。

  3. 相对层级 (Relative Level):

    在分析某个指定负责人的管辖范围时,我们将该负责人自身定义为第 1 层。其直接下属构成第 2 层,下属的下属构成第 3 层,依此类推。

任务要求

给定整个部门的层级关系数据 relations 和一个指定的负责人 designatedHeader,你需要:

  1. 分析该指定负责人 designatedHeader 的管辖范围(子树)。
  2. 统计其管辖范围内,每一“相对层级”的人数。
  3. 找出人数最多的那个层级。

最终,返回这个人数最多层级的层级编号和对应的人数

平局规则 (Tie-Breaking Rule)

如果在指定负责人的管辖范围内,有多个层级的人数相同且均为最大值,则选择层级编号最小(即最高、最靠近指定负责人)的那个层级作为结果。


输入格式

  • relations: 第一个参数,一个二维数组(或列表的列表),表示所有负责人的层级关系。

    • 0 < relations.length < 100
    • relations 中的每个元素 [id, dirHeader] 表示负责人 id 的直接上级是 dirHeader
    • 1 <= id <= relations.length + 1 (根据样例推断,ID可能不连续,但最大值与长度相关)。
    • 特例: 销售总裁的编号为 1,其 dirHeader 固定为 -1
  • designatedHeader: 第二个参数,一个整数,表示待分析的指定负责人 id

    • 输入保证 designatedHeader 是一个有效的负责人ID。

输出格式

  • 一个包含两个数字的数组或列表 [level, count]

    • level: 指定负责人名下人数最多的相对层级。
    • count: 该层级的人数。

样例说明

主样例

  • 输入:

    • relations: [[1, -1], [3, 1], [2, 1], [4, 1], [5, 1], [6, 3], [7, 3], [8, 3], [12, 6], [13, 6], [21, 13], [15, 8], [16, 8], [9, 2], [10, 2], [19, 11], [20, 11], [22, 17], [11, 5], [14, 7], [17, 9], [18, 10], [23, 21]]
    • designatedHeader: 1
  • 输出: [4, 9]

  • 解释:

    1. 首先,根据 relations 数据构建整个组织的树状结构。

    2. 我们分析指定负责人 1 (总裁) 的管辖范围,也就是整棵树。

    3. 统计其相对层级的人数:

      • 第 1 层: 仅负责人 1 自身。人数: 1
      • 第 2 层: 1 的直接下属 [2, 3, 4, 5]人数: 4
      • 第 3 层: 235 的直接下属 [6, 7, 8, 9, 10, 11]人数: 6
      • 第 4 层: 67891011 的直接下属 [12, 13, 14, 15, 16, 17, 18, 19, 20]人数: 9
      • 第 5 层: 1317 的直接下属 [21, 22]人数: 2
      • 第 6 层: 21 的直接下属 [23]人数: 1
    4. 比较各层人数 [1, 4, 6, 9, 2, 1],最大值为 9,它出现在第 4 层。

    5. 因此,输出为 [4, 9]

补充说明中的样例

  • 指定负责人 9:

    • 其管辖范围为 9 -> 17 -> 22
    • 第1层: {9} (1人), 第2层: {17} (1人), 第3层: {22} (1人)。
    • 最大人数为 1,出现在第1、2、3层。根据平局规则,取层级编号最小的,即第 1 层。
    • 输出: [1, 1]
  • 指定负责人 6:

    • 其管辖范围为 6 -> {12, 13},其中 13 -> 21 -> 23
    • 第1层: {6} (1人), 第2层: {12, 13} (2人), 第3层: {21} (1人), 第4层: {23} (1人)。
    • 最大人数为 2,出现在第 2 层。
    • 输出: [2, 2]
  • 指定负责人 20:

    • 其管辖范围仅有他自己,他没有下属。
    • 第1层: {20} (1人)。
    • 最大人数为 1,出现在第 1 层。
    • 输出: [1, 1]
import java.util.*;
import java.util.stream.Collectors;

public class Solution {
    /**
     * 内部静态类,用于表示题目给出的输入格式。
     * 在主逻辑中我们会将其转换为更易于处理的 Map 结构。
     */
    static class Relation {
        int id;
        int dirHeader;

        public Relation(int id, int dirHeader) {
            this.id = id;
            this.dirHeader = dirHeader;
        }
    }

    /**
     * 统计指定负责人名下人数最多的层级及其人数。
     *
     * @param relations        二维数组,表示 [员工ID, 直接上级ID] 的关系列表。
     * @param designatedHeader 要查询的负责人ID。
     * @return 一个包含两个元素的数组 [层级, 人数]。
     */
    public int[] getLevelWithMostPeople(int[][] relations, int designatedHeader) {
        // --- 1. 构建树形结构(邻接表) ---
        // 我们需要一个从经理指向下属的映射,方便从上到下遍历。
        // Key: 经理 ID (Integer)
        // Value: 该经理的所有直接下属 ID 列表 (List<Integer>)
        Map<Integer, List<Integer>> managerToSubordinatesMap = new HashMap<>();

        // 遍历输入的原始关系数据
        for (int[] relation : relations) {
            int subordinateId = relation[0];
            int managerId = relation[1];

            // 总裁的上级是 -1,我们只关心从经理到下属的映射
            if (managerId != -1) {
                // computeIfAbsent: 如果 managerId 不在 map 中,则创建一个新的 ArrayList并返回,
                // 否则返回现有的 List。然后将 subordinateId 加入该列表。
                managerToSubordinatesMap.computeIfAbsent(managerId, k -> new ArrayList<>()).add(subordinateId);
            }
        }

        // --- 2. 使用广度优先搜索 (BFS) 按层级统计人数 ---
        // levelCounts 列表将按顺序存储每一层的人数。
        // levelCounts.get(0) 是第1层的人数,get(1) 是第2层,以此类推。
        List<Integer> levelCounts = new ArrayList<>();

        // BFS 使用的队列,初始时只包含要查询的负责人
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(designatedHeader);

        // 只要队列不为空,就说明还有层级需要处理
        while (!queue.isEmpty()) {
            // 获取当前层的节点数量(即当前层级的人数)
            int currentLevelSize = queue.size();
            // 将当前层级的人数记录下来
            levelCounts.add(currentLevelSize);

            // 遍历当前层的所有节点
            for (int i = 0; i < currentLevelSize; i++) {
                // 从队列中取出一个当前层的负责人
                int currentManager = queue.poll();

                // 找到他所有的直接下属
                List<Integer> subordinates = managerToSubordinatesMap.getOrDefault(currentManager, Collections.emptyList());

                // 将所有直接下属加入队列,以备下一轮(下一层级)处理
                for (int sub : subordinates) {
                    queue.offer(sub);
                }
            }
        }

        // --- 3. 从层级统计结果中找出最优解 ---
        // 如果 levelCounts 为空(例如 designatedHeader 无效或不存在),
        // 尽管题目保证输入有效,但作为健壮性代码,我们可以处理这种情况。
        // 一个负责人至少包含他自己,所以至少是 [1, 1]。
        if (levelCounts.isEmpty()) {
             // 根据题目补充说明,即使没有下属,也应返回自身
             return new int[]{1, 1};
        }

        int bestLevel = 1; // 最佳层级编号 (1-based)
        int maxPeople = levelCounts.get(0); // 最多人数,初始化为第1层的人数 (即1)

        // 从第2层 (索引1) 开始遍历,寻找人数更多的层级
        for (int i = 1; i < levelCounts.size(); i++) {
            int currentLevelNumber = i + 1; // 层级编号是 1-based
            int peopleOnThisLevel = levelCounts.get(i);

            // 规则1:如果当前层级的人数更多,则更新为最佳
            if (peopleOnThisLevel > maxPeople) {
                maxPeople = peopleOnThisLevel;
                bestLevel = currentLevelNumber;
            }
            // 规则2:如果人数相同,选择最高的层级。
            // 这个规则通过只在 ">" 时更新来自然满足。如果 "==",我们不更新,
            // 从而保留了之前找到的、层级编号更小的(即更高的)那个。
        }

        // --- 4. 返回结果 ---
        return new int[]{bestLevel, maxPeople};
    }
}

DFS版本:

import java.util.*;

/**
 * 使用深度优先搜索 (DFS) 解决“销售点分布调查”问题的方案类。
 */
public class Solution_DFS {

    /**
     * 统计指定负责人名下人数最多的层级及其人数。
     *
     * @param relations        二维数组,表示 [员工ID, 直接上级ID] 的关系列表。
     * @param designatedHeader 要查询的负责人ID。
     * @return 一个包含两个元素的数组 [层级, 人数]。
     */
    public int[] getLevelWithMostPeople(int[][] relations, int designatedHeader) {

        // --- 1. 构建树形结构(邻接表)---
        // 这个步骤与 BFS 版本完全相同,因为数据预处理与遍历算法无关。
        // Key: 经理 ID (Integer)
        // Value: 该经理的所有直接下属 ID 列表 (List<Integer>)
        Map<Integer, List<Integer>> managerToSubordinatesMap = new HashMap<>();
        for (int[] relation : relations) {
            int subordinateId = relation[0];
            int managerId = relation[1];
            if (managerId != -1) {
                managerToSubordinatesMap.computeIfAbsent(managerId, k -> new ArrayList<>()).add(subordinateId);
            }
        }

        // --- 2. 使用深度优先搜索 (DFS) 按层级统计人数 ---
        // levelCounts 列表将按顺序存储每一层的人数 (索引0代表第1层)。
        List<Integer> levelCounts = new ArrayList<>();

        // 启动 DFS 遍历,从指定负责人开始,其相对层级为 1 (程序中用索引 0 表示)
        dfs(designatedHeader, 0, managerToSubordinatesMap, levelCounts);


        // --- 3. 从层级统计结果中找出最优解 ---
        // 这部分逻辑与 BFS 版本完全相同,因为它只依赖于最终的 levelCounts 结果。
        if (levelCounts.isEmpty()) {
             // 一个负责人至少包含他自己,所以至少是 [1, 1]。
             return new int[]{1, 1};
        }

        int bestLevel = 1; // 最佳层级编号 (1-based)
        int maxPeople = levelCounts.get(0); // 最多人数
        
        for (int i = 1; i < levelCounts.size(); i++) {
            int currentLevelNumber = i + 1;
            int peopleOnThisLevel = levelCounts.get(i);
            
            if (peopleOnThisLevel > maxPeople) {
                maxPeople = peopleOnThisLevel;
                bestLevel = currentLevelNumber;
            }
        }

        // --- 4. 返回结果 ---
        return new int[]{bestLevel, maxPeople};
    }

    /**
     * DFS 辅助方法,递归地遍历树并统计每层的人数。
     *
     * @param currentNode            当前正在访问的节点ID。
     * @param currentLevel           当前节点的相对层级 (0-based)。
     * @param adjList                邻接表表示的树结构。
     * @param levelCounts            用于记录各层级人数的列表。
     */
    private void dfs(int currentNode, int currentLevel, Map<Integer, List<Integer>> adjList, List<Integer> levelCounts) {
        
        // --- 确保 levelCounts 列表有足够的空间来记录当前层级 ---
        // 如果当前层级超出了列表的现有大小,需要添加新的元素来扩展它。
        while (currentLevel >= levelCounts.size()) {
            levelCounts.add(0); // 用0初始化新层级的人数
        }

        // --- 核心操作:为当前节点所在的层级增加计数 ---
        levelCounts.set(currentLevel, levelCounts.get(currentLevel) + 1);

        // --- 递归地访问所有下属 ---
        // 获取当前节点的所有直接下属
        List<Integer> subordinates = adjList.getOrDefault(currentNode, Collections.emptyList());
        
        // 对每一个下属,进行深度优先的递归调用
        // 下属的层级是当前层级 + 1
        for (int sub : subordinates) {
            dfs(sub, currentLevel + 1, adjList, levelCounts);
        }
    }
}