【华为OD-E卷 - 异常的打卡记录100分(python、java、c++、js、c)】
【华为OD-E卷 - 异常的打卡记录100分(python、java、c++、js、c)】
题目
考勤记录是分析和考核职工工作时间利用情况的原始依据,也是计算职工工资的原始依据,为了正确地计算职工工资和监督工资基金使用情况,公司决定对员工的手机打卡记录进行异常排查。
如果出现以下两种情况,则认为打卡异常:
实际设备号与注册设备号不一样 或者,同一个员工的两个打卡记录的时间小于60分钟并且打卡距离超过5km。 给定打卡记录的字符串数组 clockRecords(每个打卡记录组成为:工号;时间(分钟);打卡距离(km);实际设备号;注册设备号),返回其中异常的打卡记录(按输入顺序输出)
输入描述
- 第一行输入为N,表示打卡记录数;
之后的N行为打卡记录,每一行为一条打卡记录。
输出描述
- 输出异常的打卡记录
备注
- clockRecords长度 ≤ 1000 clockRecords[i] 格式:{id},{time},{distance},{actualDeviceNumber},{registeredDeviceNumber} id由6位数字组成 time由整数组成,范围为0 ~ 1000 distance由整数组成,范围为0 ~100 actualDeviceNumber与registeredDeviceNumber由思维大写字母组成
用例
用例一:
输入:
2
100000,10,1,ABCD,ABCD
100000,50,10,ABCD,ABCD
输出:
100000,10,1,ABCD,ABCD;100000,50,10,ABCD,ABCD
用例二:
输入:
2
100000,10,1,ABCD,ABCD
100001,80,10,ABCE,ABCE
输出:
null
用例三:
输入:
2
100000,10,1,ABCD,ABCD
100000,80,10,ABCE,ABCD
输出:
100000,80,10,ABCE,ABCD
python解法
- 解题思路:
- 数据结构选择:
使用 emp 字典来存储每个员工的最后一次打卡信息。键为员工 ID (eid),值为三元组 (最后一次打卡时间, 最后一次打卡地点, 当前记录的索引)。
使用 result 集合来存储异常记录的索引。
遍历记录:
对于每条记录,首先判断报告地址 (ad) 是否与打卡地址 (rd) 一致,如果不一致,则这条记录是异常的,加入到 result 集合中。
然后,检查该员工的前一次打卡记录,若满足打卡时间差小于 60 分钟且打卡地点相差超过 5 米的条件,则认为这两次打卡是异常的,当前记录与之前的记录都应该加入异常记录集合中。
输出结果:
若存在异常记录,则返回按要求格式化的结果,返回所有异常记录的信息,按照异常记录的索引升序排列。
如果没有异常记录,返回 “null”。
# 输入读取
n = int(input()) # 输入记录数量
cr = [input().split(",") for _ in range(n)] # 读取n条记录,存储为列表,每条记录是一个列表
def find_abnormal(cr):
emp = {} # 用来存储每个员工的最后一次打卡信息
result = set() # 用来存储异常记录的索引
# 遍历所有的打卡记录
for i in range(len(cr)):
# 从当前记录中提取员工ID、时间、地点等信息
eid, t, d, ad, rd = cr[i]
# 1. 判断报告地址与打卡地址是否不一致
if ad != rd:
result.add(i) # 若不一致,加入异常记录集
# 2. 检查是否存在两次打卡时间小于60分钟且地点相差超过5米的情况
if eid in emp:
last_t, last_d, idx = emp[eid] # 获取该员工上次的打卡记录
# 判断时间差小于60分钟且地点差异大于5米
if int(t) - last_t < 60 and abs(int(d) - last_d) > 5:
result.add(i) # 当前记录异常
result.add(idx) # 上次记录异常
# 更新该员工的打卡信息
emp[eid] = (int(t), int(d), i) # 存储最新的时间、地点和当前记录的索引
# 如果有异常记录,按照记录索引升序排列并输出
if result:
return ";".join(",".join(cr[i]) for i in sorted(result)) # 格式化输出异常记录
return "null" # 若无异常记录,返回 "null"
# 输出结果
print(find_abnormal(cr))
java解法
- 解题思路
- 此题要求对员工的打卡记录进行分析,找出异常记录。异常的判断标准有两个:
打卡地址与报告地址不一致,这条记录是异常的。
打卡时间差小于 60 分钟且地点差异超过 5 米,这两条记录是异常的。
解题步骤:
数据存储:
empData:一个哈希表,用于存储每个员工的打卡记录。键是员工 ID(eid),值是该员工所有的打卡记录列表。
errorIdx:一个集合,用于存储异常记录的索引。
解析记录:
对于每条记录,首先检查报告地址与打卡地址是否一致。如果不一致,将该记录的索引加入异常记录集合 errorIdx。
然后,遍历每个员工的记录,检查相邻的打卡记录,如果时间差小于 60 分钟且地点差异超过 5 米,则认为这两次打卡是异常的,添加到异常记录集合。
排序和输出:
对每个员工的记录按照时间进行排序,然后进行时间差和地点差的检查。
如果有异常记录,按要求格式化输出异常记录;如果没有,输出 “null”。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt(); // 读取打卡记录的数量
String[][] logs = new String[n][]; // 用于存储所有打卡记录
// 读取所有打卡记录
for (int i = 0; i < n; i++) {
logs[i] = input.next().split(","); // 每条记录按逗号分割
}
System.out.println(findAnomalies(logs)); // 调用方法找出异常记录并输出
}
// 查找异常打卡记录
public static String findAnomalies(String[][] logs) {
HashMap<String, List<String[]>> empData = new HashMap<>(); // 存储每个员工的打卡记录
Set<Integer> errorIdx = new TreeSet<>(); // 存储异常记录的索引
// 处理所有记录
for (int i = 0; i < logs.length; i++) {
String[] log = Arrays.copyOf(logs[i], logs[i].length + 1); // 复制记录并扩展,添加记录的索引
log[log.length - 1] = String.valueOf(i); // 添加当前记录的索引
// 1. 如果报告地址与打卡地址不一致,加入异常记录
if (!log[3].equals(log[4])) {
errorIdx.add(i); // 将当前记录索引加入异常记录集合
}
// 2. 将打卡记录按照员工 ID 存入 empData
empData.computeIfAbsent(log[0], k -> new ArrayList<>()).add(log); // 按员工 ID 存储记录
}
// 遍历每个员工的打卡记录,查找符合异常条件的记录
for (String key : empData.keySet()) {
List<String[]> records = empData.get(key); // 获取当前员工的打卡记录
records.sort(Comparator.comparingInt(a -> Integer.parseInt(a[1]))); // 按照时间排序
// 检查每两条记录之间的时间差和地点差
for (int i = 0; i < records.size(); i++) {
int t1 = Integer.parseInt(records.get(i)[1]); // 第一条记录的时间
int d1 = Integer.parseInt(records.get(i)[2]); // 第一条记录的地点
// 与之后的记录进行比较
for (int j = i + 1; j < records.size(); j++) {
int t2 = Integer.parseInt(records.get(j)[1]); // 第二条记录的时间
int d2 = Integer.parseInt(records.get(j)[2]); // 第二条记录的地点
// 如果时间差大于等于60分钟,则跳出循环
if (t2 - t1 >= 60) break;
// 如果时间差小于60分钟,且地点差超过5米,认为两条记录异常
if (Math.abs(d2 - d1) > 5) {
errorIdx.add(Integer.parseInt(records.get(i)[5])); // 添加第一条记录的索引
errorIdx.add(Integer.parseInt(records.get(j)[5])); // 添加第二条记录的索引
}
}
}
}
// 如果没有异常记录,返回 "null"
if (errorIdx.isEmpty()) return "null";
// 格式化输出异常记录
StringJoiner sj = new StringJoiner(";");
for (int idx : errorIdx) {
sj.add(String.join(",", logs[idx])); // 将异常记录按照要求的格式连接起来
}
return sj.toString(); // 返回格式化后的异常记录
}
}
C++解法
- 解题思路
更新中
C解法
更新中
JS解法
报告地址和打卡地址不一致。
同一员工的打卡记录之间,时间差小于 60 分钟,且地点差异大于 5 米。
步骤:
输入数据处理:
记录的格式为 [员工ID, 打卡时间, 打卡地点, 预计打卡地点, 报告打卡地点]。
需要遍历每一条记录,并检查打卡地址是否与报告地址一致。
需要将每一条记录按照员工 ID 分类存储,以便后续对同一员工的打卡记录进行时间和地点差的检查。
判断异常条件:
如果报告地址与打卡地址不一致,记录为异常。
对于每个员工的记录,按照打卡时间升序排序,然后检查相邻记录的时间差和地点差。
如果时间差小于 60 分钟且地点差大于 5 米,则这两条记录都标记为异常。
输出:
如果有异常记录,输出异常记录的内容,按要求格式化(记录间用 ; 分隔)。
如果没有异常记录,输出 “null”。
// 找出所有异常的打卡记录
function findAnomalies(logs) {
// 用 Map 存储员工的所有打卡记录,键是员工ID,值是该员工的所有打卡记录
const empData = new Map();
// 用 Set 存储异常记录的索引
const errorIdx = new Set();
// 遍历每条记录
logs.forEach((log, i) => {
// 复制记录并添加记录的索引,以便后续引用
const extendedLog = [...log, i.toString()];
// 1. 如果报告地址与打卡地址不一致,标记异常
if (extendedLog[3].trim() !== extendedLog[4].trim()) {
errorIdx.add(i); // 将异常记录的索引添加到 errorIdx
}
// 2. 按员工ID将记录分类存储
if (!empData.has(extendedLog[0])) {
empData.set(extendedLog[0], []); // 如果没有该员工,初始化为一个空数组
}
empData.get(extendedLog[0]).push(extendedLog); // 将记录加入该员工的记录列表
});
// 对每个员工的记录进行处理
for (const key of empData.keys()) {
const records = empData.get(key); // 获取该员工的所有打卡记录
// 按打卡时间升序排序
records.sort((a, b) => parseInt(a[1]) - parseInt(b[1]));
// 遍历员工的打卡记录,检查时间和地点差
for (let i = 0; i < records.length; i++) {
const t1 = parseInt(records[i][1]); // 获取第一个记录的时间
const d1 = parseInt(records[i][2]); // 获取第一个记录的地点
// 与之后的记录进行比较
for (let j = i + 1; j < records.length; j++) {
const t2 = parseInt(records[j][1]); // 获取第二个记录的时间
const d2 = parseInt(records[j][2]); // 获取第二个记录的地点
// 如果时间差大于等于60分钟,则跳出循环
if (t2 - t1 >= 60) break;
// 如果时间差小于60分钟,且地点差超过5米,认为两条记录异常
if (Math.abs(d2 - d1) > 5) {
// 标记两条记录为异常
errorIdx.add(parseInt(records[i][5]));
errorIdx.add(parseInt(records[j][5]));
}
}
}
}
// 如果没有异常记录,返回 "null"
if (errorIdx.size === 0) return "null";
// 格式化输出异常记录
const result = Array.from(errorIdx)
.map(idx => logs[idx].join(",")) // 将每条异常记录转换为字符串
.join(";"); // 用分号连接所有异常记录
return result; // 返回格式化后的异常记录
}
// 读取输入数据并调用 findAnomalies 方法
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// 询问输入的记录数量 n
rl.question('', (n) => {
const logs = [];
let count = 0;
// 监听每一行输入
rl.on('line', (input) => {
if (input.trim() === '') return; // 如果输入为空则跳过
logs.push(input.split(",")); // 将输入的记录按逗号分割,保存到 logs 数组
count++;
if (count === parseInt(n)) { // 如果输入的记录数量达到 n
console.log(findAnomalies(logs)); // 调用 findAnomalies 输出结果
rl.close(); // 关闭输入流
}
});
});
注意:
如果发现代码有用例覆盖不到的情况,欢迎反馈!会在第一时间修正,更新。
解题不易,如对您有帮助,欢迎点赞/收藏