
华为OD机试 新系统 题库疯狂收录中,刷题点这里
专栏导读
本专栏收录于《华为OD机试(JAVA)真题》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。
一、题目描述
某物流企业在全国多个仓库开展仓储业务,需要对不同仓库、不同时段的入库成本、出库收入和库存损耗进行综合统计分析,用于生成
日报、周报和月报。
字段说明及取值范围如下:
| 字段 | 类型 | 取值范围 | 说明 |
|---|---|---|---|
| warehouseId | int | [0, 99] | 仓库编号 |
| numOfWarehouse | int | [0, 99] | 仓库数量 |
| startDay | int | [0, 99] | 统计开始天 |
| endDay | int | [0, 99] | 统计结束天,startDay ≤ endDay |
| inCost | int | [0, 1000] | 入库成本(千元) |
| outRevenue | int | [0, 2000] | 出库收入(千元) |
| loss | int | [0, 100] | 库存损耗(千元),0表示无损耗 |
二、输入描述
warehouses: int[] 扁平化数组 [w0d0in, w0d0out, w0d0loss, w0d1in, w0d1out, w0d1loss, …],所有仓库数据都存在该数据中,先存储仓库0的数据,再存储仓库1的数据,所有仓库数据个数均相同
queries: int[][] [[warehouseId, startDay, endDay], …]
numOfWarehouse: int 仓库个数
输入保证最多100个仓库,每个仓库最多100天数据,每天数据连续,中间无空缺
三、输出描述
返回 int[][] 每个查询结果:[累计净利润, 累计损耗, 风控标记]
业务计算规则:
净利润 = 累计出库收入 - 累计入库成本
累计损耗 = Σ每日损耗
损耗率 = 累计损耗 / (累计入库成本 + 累计出库收入) × 100%,保留2位小数
风控规则:
当损耗率 > 5% 时,触发风控标记(返回1)
当损耗率 ≤ 5% 时,不触发风控(返回0)
四、测试用例
测试用例1:
1、输入
3,8,1,5,12,0,2,6,0,4,10,1
0,0,2
1
2、输出
16,1,0
3、说明
查询仓库0的第0~2天:
累计入库成本 = 3 + 5 + 2 = 10
累计出库收入 = 8 + 12 + 6 = 26
净利润 = 26 - 10 = 16
累计损耗 = 1
损耗率 = 1 / 36 × 100% ≈ 2.78%
不超过5%,因此风控标记为0。
测试用例2:
1、输入
10,20,5,10,20,5,10,20,5,5,15,2,5,15,2,5,15,2
0,0,2 1,0,1
2
2、输出
30,15,1 20,4,1
3、说明
每个仓库有3天数据。
第一个查询:
仓库0,第0~2天
累计入库 = 30
累计收入 = 60
净利润 = 30
累计损耗 = 15
损耗率 = 15 / 90 × 100% = 16.67%
触发风控。
第二个查询:
仓库1,第0~1天
累计入库 = 10
累计收入 = 30
净利润 = 20
累计损耗 = 4
损耗率 = 4 / 40 × 100% = 10.00%
同样触发风控。
五、解题思路
本题采用 二维前缀和 来处理每个仓库的区间统计查询。
由于所有仓库每天都有 入库成本、出库收入、库存损耗 3 项数据,并且每天连续,因此可以分别为每个仓库维护三组前缀和数组:
inPrefix:累计入库成本
outPrefix:累计出库收入
lossPrefix:累计库存损耗
预处理完成后,对于查询 [warehouseId, startDay, endDay],可以通过:区间累计值 = prefix[endDay + 1] - prefix[startDay],直接得到指定时间段的累计入库成本、累计出库收入和累计损耗,然后计算:累计净利润 = 累计出库收入 - 累计入库成本
损耗率 = 累计损耗 / (累计入库成本 + 累计出库收入) × 100%
根据损耗率是否大于 5% 得到风控标记。
六、Java算法源码
public class OdTest {
// 将一段由逗号、空格混合分隔的整数文本转换为 int 数组
private static int[] parseNumbers(String text) {
text = text.trim();
if (text.isEmpty()) {
return new int[0];
}
String[] parts = text.split("[,\\s]+");
int[] nums = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
nums[i] = Integer.parseInt(parts[i]);
}
return nums;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 第一部分:所有仓库的扁平化数据
int[] warehouses = parseNumbers(scanner.nextLine());
int[] queryNums = parseNumbers(scanner.nextLine());
// 最后一行:仓库数量
int numOfWarehouse = Integer.parseInt(scanner.nextLine());
if (numOfWarehouse <= 0 || warehouses.length == 0) {
System.out.println();
return;
}
/*
* 每个仓库每天固定有:
* inCost、outRevenue、loss
* 共 3 个整数。
*
* 所有仓库天数相同,因此可以反推出每个仓库有多少天数据。
*/
int days = warehouses.length / (numOfWarehouse * 3);
/*
* prefix[w][d] 表示:
* 仓库 w 前 d 天的累计值。
*
* 多设置一个位置 0,可以方便计算从第 0 天开始的区间。
*/
int[][] inPrefix = new int[numOfWarehouse][days + 1];
int[][] outPrefix = new int[numOfWarehouse][days + 1];
int[][] lossPrefix = new int[numOfWarehouse][days + 1];
// 构造三个前缀和数组
for (int w = 0; w < numOfWarehouse; w++) {
for (int d = 0; d < days; d++) {
/*
* warehouses 按:
*
* 仓库0所有天、
* 仓库1所有天、
* ...
*
* 顺序排列。
*
* 每天占 3 个位置。
*/
int index = (w * days + d) * 3;
inPrefix[w][d + 1] = inPrefix[w][d] + warehouses[index];
outPrefix[w][d + 1] = outPrefix[w][d] + warehouses[index + 1];
lossPrefix[w][d + 1] = lossPrefix[w][d] + warehouses[index + 2];
}
}
StringBuilder ans = new StringBuilder();
// 每三个数字组成一个查询
for (int i = 0; i + 2 < queryNums.length; i += 3) {
int warehouseId = queryNums[i];
int startDay = queryNums[i + 1];
int endDay = queryNums[i + 2];
/*
* 使用前缀和计算闭区间 [startDay, endDay]。
* 只需要两次数组访问和一次减法。
*/
int totalIn = inPrefix[warehouseId][endDay + 1] - inPrefix[warehouseId][startDay];
int totalOut = outPrefix[warehouseId][endDay + 1] - outPrefix[warehouseId][startDay];
int totalLoss = lossPrefix[warehouseId][endDay + 1] - lossPrefix[warehouseId][startDay];
// 净利润 = 累计收入 - 累计成本
int netProfit = totalOut - totalIn;
// 损耗率的分母
int totalAmount = totalIn + totalOut;
int risk = 0;
if (totalAmount != 0) {
/*
* 损耗率:
*
* totalLoss / totalAmount * 100%
*
* 题目要求保留两位小数。
*
* 将百分数扩大 100 倍:
* 5.00% -> 500
* 5.01% -> 501
*
* 所以需要计算:
* round(totalLoss * 10000 / totalAmount)
*
* 这里全部使用整数完成四舍五入,
* 避免 double 在 5% 临界值附近出现误差。
*/
long numerator = (long) totalLoss * 10000;
long roundedRate = (numerator * 2 + totalAmount) / (2L * totalAmount);
// 保留两位小数以后严格大于 5.00% 才触发
risk = roundedRate > 500 ? 1 : 0;
}
if (ans.length() > 0) {
ans.append(' ');
}
ans.append(netProfit)
.append(',')
.append(totalLoss)
.append(',')
.append(risk);
}
System.out.println(ans);
}
}
七、效果展示
1、输入
5,10,0,8,12,1,20,15,2,10,30,0,100,120,5,50,40,1,0,20,2,30,90,3
0,1,3 1,2,3
2
2、输出
19,3,0 80,5,0
3、说明
每个仓库4天。
第一个查询:仓库0,第1~3天
累计入库 = 8 + 20 + 10 = 38
累计收入 = 12 + 15 + 30 = 57
净利润 = 19
累计损耗 = 1 + 2 + 0 = 3
损耗率 = 3 / 95 × 100% ≈ 3.16%
输出:19,3,0
第二个查询:
仓库1,第2~3天
累计入库 = 0 + 30 = 30
累计收入 = 20 + 90 = 110
净利润 = 80
累计损耗 = 2 + 3 = 5
损耗率 = 5 / 140 × 100% ≈ 3.57%
输出:80,5,0

🏆下一篇:华为OD机试 - 简易内存池 - 逻辑分析(Java 新系统 200分)
🏆本专栏收录于《华为OD机试(JAVA)真题》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。

45万+

被折叠的 条评论
为什么被折叠?



