青训营X豆包MarsCode 技术训练营5| 豆包MarsCode AI 刷题

35 阅读7分钟

问题描述

在一款多人游戏中,每局比赛需要多个玩家参与。如果发现两名玩家至少一起玩过两局比赛,则可以认为这两名玩家互为队友。现在你有一份玩家(通过玩家ID标识)和比赛局次(通过比赛ID标识)的历史记录表,目标是帮助某位指定玩家找到所有符合条件的队友。

例如,已知以下比赛历史记录:

| 玩家ID | 游戏ID |

测试样例

样例1:

输入:id = 1, num = 10, array = [[1,1], [1,2], [1,3], [2,1], [2,4], [3,2], [4,1], [4,2], [5,2], [5,3]]
输出:[4, 5]

样例2:

输入:id = 2, num = 6, array = [[2,1], [2,3], [1,1], [1,2], [3,1], [4,3]]
输出:[]

样例3:

输入:id = 3, num = 8, array = [[3,1], [3,2], [3,3], [4,1], [5,2], [6,3], [7,1], [7,2]]
输出:[7]

题目解析

思路

这道题目的核心思路在于通过对给定的玩家与比赛局次历史记录进行分析处理,先梳理出每个玩家参与过的比赛,然后找出指定玩家参与的比赛,接着对比其他玩家与该指定玩家共同参与比赛的次数,筛选出共同参与比赛次数至少为 2 次的其他玩家,将其作为符合条件的队友,最后把这些队友的 ID 整理并按升序排列输出。

具体来看,首先要遍历整个比赛历史记录数组,将每个玩家及其参与的比赛局次信息进行整理存储,构建起每个玩家对应参与比赛集合的映射关系。之后,聚焦到指定玩家,获取其参与的比赛集合。再去遍历所有玩家的比赛记录,针对每个其他玩家,查看他们参与的比赛中有多少是和指定玩家重合的,也就是计算共赛次数,每发现一次重合就对共赛次数计数加 1。当遍历完其他玩家的比赛记录后,那些共赛次数达到 2 次及以上的其他玩家,就被认定为符合条件的队友,将他们的 ID 提取出来并进行升序排序,最终得到符合要求的队友 ID 数组作为结果。

例如,对于样例 1 中输入的信息,先是把所有玩家参与的比赛情况记录好,比如玩家 1 参与了比赛 1、2、3 等。然后确定指定玩家(这里是玩家 1)参与的比赛,再去看其他玩家(像玩家 2、3、4、5 等)和玩家 1 的共赛情况,经过统计发现玩家 4 和玩家 5 与玩家 1 的共赛次数都达到了 2 次,所以最终输出的队友数组就是 [4, 5]。

代码详解

import java.util.*;

public class Main {
    public static int[] solution(int id, int num, int[][] array) {
        // 创建一个 Map 以存储每个玩家参与的比赛
        Map<Integer, Set<Integer>> playerMatches = new HashMap<>();
        
        // 遍历比赛记录
        for (int[] record : array) {
            int playerId = record[0];
            int gameId = record[1];
            
            // 如果该玩家还没有记录比赛,初始化一个新的 Set
            playerMatches.putIfAbsent(playerId, new HashSet<>());
            // 将比赛ID加入该玩家的比赛记录
            playerMatches.get(playerId).add(gameId);
        }
        
        // 找到指定玩家的比赛
        Set<Integer> playerGames = playerMatches.getOrDefault(id, new HashSet<>());
        
        // 统计其他玩家与指定玩家的共赛次数
        Map<Integer, Integer> teammateCounts = new HashMap<>();
        
        // 遍历所有玩家的比赛记录
        for (Map.Entry<Integer, Set<Integer>> entry : playerMatches.entrySet()) {
            int playerId = entry.getKey();
            // 跳过指定玩家自己
            if (playerId == id) continue;
            
            // 计算与指定玩家的共赛次数
            int count = 0;
            for (int gameId : entry.getValue()) {
                if (playerGames.contains(gameId)) {
                    count++;
                }
            }
            // 如果共赛次数 >= 2,则记录下来
            if (count >= 2) {
                teammateCounts.put(playerId, count);
            }
        }

        // 返回符合条件的队友ID
        int[] result = teammateCounts.keySet().stream().mapToInt(i -> i).toArray();
        Arrays.sort(result); // 按照升序排列结果
        return result;
    }

    public static void main(String[] args) {
        // Add your test cases here
        System.out.println(Arrays.equals(solution(1, 10, new int[][]{{1, 1}, {1, 2}, {1, 3}, {2, 1}, {2, 4}, {3, 2}, {4, 1}, {4, 2}, {5, 2}, {5, 3}}), new int[]{4, 5}));
        System.out.println(Arrays.equals(solution(2, 6, new int[][]{{2, 1}, {2, 3}, {1, 1}, {1, 2}, {3, 1}, {4, 3}}), new int[]{}));
        System.out.println(Arrays.equals(solution(3, 8, new int[][]{{3, 1}, {3, 2}, {3, 3}, {4, 1}, {5, 2}, {6, 3}, {7, 1}, {7, 2}}), new int[]{7}));
    }
}

代码详解

在 solution 方法中,首先创建了一个 Map<Integer, Set<Integer>> 类型的 playerMatches ,用于存储每个玩家参与的比赛情况,键是玩家 ID,值是该玩家参与的比赛 ID 集合。通过遍历 array 这个比赛记录二维数组,对于每条记录,先提取出玩家 ID 和比赛 ID,如果发现某个玩家还没有对应的比赛记录集合,就利用 putIfAbsent 方法初始化一个新的 HashSet ,然后把当前比赛 ID 添加到该玩家对应的集合中。

接着,通过 getOrDefault 方法获取指定玩家的比赛集合 playerGames ,如果指定玩家不存在记录,则返回一个空的 HashSet 。之后又创建了 Map<Integer, Integer> 类型的 teammateCounts ,用于统计其他玩家与指定玩家的共赛次数,键是其他玩家 ID,值是共赛次数。

再通过一个循环遍历 playerMatches 的所有键值对,对于每个其他玩家(跳过指定玩家自己),循环其参与的比赛 ID,只要发现该比赛 ID 也在指定玩家的比赛集合中,就对共赛次数计数加 1。当共赛次数大于等于 2 时,把该玩家 ID 和共赛次数添加到 teammateCounts 中。

最后,利用 Java 8 的流式操作,从 teammateCounts 的键集合中获取符合条件的队友 ID 并转换为数组 result ,再通过 Arrays.sort 方法对结果数组进行升序排序,最终返回这个排序好的队友 ID 数组。

在 main 方法中,给出了几个不同输入情况的测试样例,通过调用 solution 方法并对比输出结果与预期的数组是否相等,以此来验证代码的正确性,这种测试方式能帮助我们及时发现代码在处理不同玩家与比赛记录组合时可能存在的逻辑问题,确保代码功能的准确性。

知识总结

通过刷这道题,我们收获了不少重要知识点以及有了更深的理解。

一是对集合框架(如 HashMap 和 HashSet )的运用更加熟练了。在实际编程中,很多时候需要存储和管理一组相关的数据,像本题中利用 HashMap 来建立玩家与他们参与比赛集合之间的映射关系,以及使用 HashSet 来确保比赛 ID 的唯一性,避免重复记录。集合框架提供了方便高效的数据存储和操作方式,对于处理复杂的数据关联场景非常有帮助,理解它们的特性、方法以及适用场景,能让我们在面对类似问题时更得心应手地组织和处理数据。

二是对 Java 8 中流式操作和 Lambda 表达式(虽然本题中 Lambda 表达式体现不太明显,但流式操作底层依赖于此)的认识进一步加深。通过流式操作,如 keySet().stream().mapToInt(i -> i).toArray() 这样简洁的代码就能实现从 Map 的键集合转换为数组的功能,相比于传统的遍历方式更加简洁高效,代码的可读性在一定程度上也有所提升。这让我们明白在合适的场景下运用这些新特性可以优化代码结构,提升编程效率。

对于入门的同学,我的学习建议是,首先要扎实学习集合框架的基础知识,包括各种集合类(如 ArrayList 、LinkedList 、HashMap 、HashSet 等)的创建、添加元素、获取元素、遍历等基本操作,可以通过一些简单的练习,比如利用 HashMap 存储学生姓名和成绩,然后进行成绩查询、排序等操作,来熟悉集合的使用。对于 Java 8 的新特性,要先从基础的语法入手,了解流式操作的常见方法(如 filter 、map 、reduce 等)以及 Lambda 表达式的基本语法和作用,多去看一些示例代码,体会它们是如何简化代码逻辑的,然后自己动手尝试在一些简单场景中运用这些特性进行编程实践。