
专栏导读
本专栏收录于《华为OD机试真题(Python/JS/C/C++)》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。
一、题目描述
某物流企业在全国多个仓库开展仓储业务,需要对不同仓库、不同时段的入库成本、出库收入和库存损耗进行综合统计分析,用于生成
日报、周报和月报。
字段说明及取值范围如下:
| 字段 | 类型 | 取值范围 | 说明 |
|---|---|---|---|
| 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% 得到风控标记。
六、Python算法源码
import sys
# 将一段由逗号、空格混合分隔的整数文本转换为整数列表
def parse_numbers(text):
text = text.strip()
if not text:
return []
# 先把逗号替换为空格,再通过 split() 处理一个或多个空白字符
return [int(x) for x in text.replace(',', ' ').split()]
def main():
# 与原 Java 代码保持一致:固定读取三行
# 第一行:所有仓库的扁平化数据
warehouses = parse_numbers(sys.stdin.readline())
# 第二行:所有查询,每三个数字组成一个查询
query_nums = parse_numbers(sys.stdin.readline())
# 第三行:仓库数量
third_line = sys.stdin.readline().strip()
num_of_warehouse = int(third_line) if third_line else 0
if num_of_warehouse <= 0 or len(warehouses) == 0:
print()
return
"""
每个仓库每天固定有:
inCost、outRevenue、loss
共 3 个整数。
所有仓库的数据天数相同,因此可以根据扁平数组总长度
反推出每个仓库的数据天数。
"""
days = len(warehouses) // (num_of_warehouse * 3)
"""
prefix[w][d] 表示:
仓库 w 前 d 天的累计值。
数组长度设置为 days + 1,第 0 个位置固定为 0。
这样查询包含第 0 天时也可以直接使用前缀和公式,
不需要额外进行边界判断。
"""
in_prefix = [
[0] * (days + 1)
for _ in range(num_of_warehouse)
]
out_prefix = [
[0] * (days + 1)
for _ in range(num_of_warehouse)
]
loss_prefix = [
[0] * (days + 1)
for _ in range(num_of_warehouse)
]
# 构造三组前缀和
for w in range(num_of_warehouse):
for d in range(days):
"""
warehouses 按以下顺序存储:
仓库0第0天、仓库0第1天……
仓库1第0天、仓库1第1天……
每天固定占用三个位置:
inCost、outRevenue、loss
"""
index = (w * days + d) * 3
in_prefix[w][d + 1] = (
in_prefix[w][d]
+ warehouses[index]
)
out_prefix[w][d + 1] = (
out_prefix[w][d]
+ warehouses[index + 1]
)
loss_prefix[w][d + 1] = (
loss_prefix[w][d]
+ warehouses[index + 2]
)
ans = []
# 每三个数字构成一个查询:
# warehouseId、startDay、endDay
for i in range(0, len(query_nums) - 2, 3):
warehouse_id = query_nums[i]
start_day = query_nums[i + 1]
end_day = query_nums[i + 2]
"""
使用前缀和计算闭区间 [start_day, end_day]:
区间和 =
prefix[end_day + 1] - prefix[start_day]
因此每个查询只需要 O(1) 时间。
"""
total_in = (
in_prefix[warehouse_id][end_day + 1]
- in_prefix[warehouse_id][start_day]
)
total_out = (
out_prefix[warehouse_id][end_day + 1]
- out_prefix[warehouse_id][start_day]
)
total_loss = (
loss_prefix[warehouse_id][end_day + 1]
- loss_prefix[warehouse_id][start_day]
)
# 净利润 = 累计出库收入 - 累计入库成本
net_profit = total_out - total_in
# 损耗率计算的分母
total_amount = total_in + total_out
risk = 0
if total_amount != 0:
"""
损耗率:
total_loss / total_amount * 100%
题目要求保留两位小数。
将百分比进一步扩大 100 倍:
5.00% -> 500
5.01% -> 501
所以计算:
round(total_loss * 10000 / total_amount)
这里不用浮点数,而是通过整数运算实现四舍五入,
避免 5% 临界值附近出现浮点精度问题。
"""
numerator = total_loss * 10000
rounded_rate = (
numerator * 2 + total_amount
) // (
2 * total_amount
)
# 保留两位小数后严格大于 5.00% 才触发风控
risk = 1 if rounded_rate > 500 else 0
ans.append(
f"{net_profit},{total_loss},{risk}"
)
print(' '.join(ans))
if __name__ == '__main__':
main()
七、JavaScript算法源码
const fs = require('fs');
// 将逗号、空格等混合分隔的字符串转换为整数数组
function parseNumbers(text) {
text = text.trim();
if (text.length === 0) {
return [];
}
// 正则 [,\\s]+ 表示一个或多个逗号/空白字符
return text.split(/[,\s]+/).map(Number);
}
// 读取标准输入
const lines = fs.readFileSync(0, 'utf8').split(/\r?\n/);
// 与原 Java 代码保持一致:固定读取前三行
// 第一行:仓库扁平数据
const warehouses = parseNumbers(lines[0] || '');
// 第二行:查询数据
const queryNums = parseNumbers(lines[1] || '');
// 第三行:仓库数量
const numOfWarehouse =
Number((lines[2] || '').trim() || 0);
if (numOfWarehouse <= 0 || warehouses.length === 0) {
console.log('');
process.exit(0);
}
/*
* 每个仓库每天固定存储三个字段:
*
* inCost
* outRevenue
* loss
*
* 因此:
*
* 总数据个数 = 仓库数量 × 天数 × 3
*
* 可以反推出每个仓库的数据天数。
*/
const days =
Math.floor(
warehouses.length
/ (numOfWarehouse * 3)
);
/*
* prefix[w][d] 表示仓库 w 前 d 天的累计数据。
*
* 多申请一个位置 0,可以方便计算从第 0 天开始的区间。
*/
const inPrefix = Array.from(
{ length: numOfWarehouse },
() => Array(days + 1).fill(0)
);
const outPrefix = Array.from(
{ length: numOfWarehouse },
() => Array(days + 1).fill(0)
);
const lossPrefix = Array.from(
{ length: numOfWarehouse },
() => Array(days + 1).fill(0)
);
// 构造三组前缀和
for (let w = 0; w < numOfWarehouse; w++) {
for (let d = 0; d < days; d++) {
/*
* 所有数据按照:
*
* 仓库0所有天
* 仓库1所有天
* ...
*
* 的顺序排列。
*
* 每一天固定占用三个位置。
*/
const 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];
}
}
const ans = [];
// 每三个数字构成一个查询
for (
let i = 0;
i + 2 < queryNums.length;
i += 3
) {
const warehouseId = queryNums[i];
const startDay = queryNums[i + 1];
const endDay = queryNums[i + 2];
/*
* 使用前缀和计算闭区间 [startDay, endDay]:
*
* 区间累计值 =
* prefix[endDay + 1] - prefix[startDay]
*
* 每个查询的时间复杂度为 O(1)。
*/
const totalIn =
inPrefix[warehouseId][endDay + 1]
- inPrefix[warehouseId][startDay];
const totalOut =
outPrefix[warehouseId][endDay + 1]
- outPrefix[warehouseId][startDay];
const totalLoss =
lossPrefix[warehouseId][endDay + 1]
- lossPrefix[warehouseId][startDay];
// 净利润 = 累计收入 - 累计成本
const netProfit =
totalOut - totalIn;
const totalAmount =
totalIn + totalOut;
let risk = 0;
if (totalAmount !== 0) {
/*
* 损耗率:
*
* totalLoss / totalAmount * 100%
*
* 将百分比保留两位以后再扩大 100 倍:
*
* 5.00% -> 500
* 5.01% -> 501
*
* 使用整数公式实现四舍五入,
* 避免直接使用浮点数产生临界值精度问题。
*/
const numerator =
totalLoss * 10000;
const roundedRate =
Math.floor(
(
numerator * 2
+ totalAmount
)
/
(
2 * totalAmount
)
);
// 严格超过 5.00% 才触发风控
risk =
roundedRate > 500
? 1
: 0;
}
ans.push(
`${netProfit},${totalLoss},${risk}`
);
}
console.log(ans.join(' '));
八、C算法源码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
/*
* 动态读取一整行。
*
* 由于仓库最多100个,每个仓库最多100天,
* 扁平数组对应的输入字符串可能比较长,
* 因此不使用固定长度的 char 数组,避免输入被截断。
*/
static char *read_line(void) {
size_t capacity = 1024;
size_t length = 0;
char *buffer =
(char *)malloc(capacity);
if (buffer == NULL) {
return NULL;
}
int ch;
while (
(ch = getchar()) != EOF
&& ch != '\n'
) {
// 空间不足时扩大到原来的2倍
if (length + 1 >= capacity) {
capacity *= 2;
char *tmp =
(char *)realloc(
buffer,
capacity
);
if (tmp == NULL) {
free(buffer);
return NULL;
}
buffer = tmp;
}
buffer[length++] = (char)ch;
}
if (ch == EOF && length == 0) {
free(buffer);
return NULL;
}
buffer[length] = '\0';
return buffer;
}
/*
* 将由逗号、空格等字符混合分隔的文本
* 转换为整数数组。
*/
static int *parse_numbers(
const char *text,
int *count
) {
int capacity = 32;
int *nums =
(int *)malloc(
sizeof(int) * capacity
);
*count = 0;
if (nums == NULL) {
return NULL;
}
const char *p = text;
while (*p) {
/*
* 跳过所有分隔符:
* 逗号、空格、Tab等。
*/
while (
*p
&& (
*p == ','
|| isspace(
(unsigned char)*p
)
)
) {
p++;
}
if (!*p) {
break;
}
char *end;
long value =
strtol(p, &end, 10);
if (p == end) {
break;
}
// 数组容量不足时动态扩容
if (*count >= capacity) {
capacity *= 2;
int *tmp =
(int *)realloc(
nums,
sizeof(int) * capacity
);
if (tmp == NULL) {
free(nums);
return NULL;
}
nums = tmp;
}
nums[(*count)++] =
(int)value;
p = end;
}
return nums;
}
int main(void) {
/*
* 与原 Java 代码保持一致:
* 固定读取三行。
*/
char *line1 = read_line();
char *line2 = read_line();
char *line3 = read_line();
if (
line1 == NULL
|| line2 == NULL
|| line3 == NULL
) {
free(line1);
free(line2);
free(line3);
return 0;
}
int warehouseCount = 0;
int queryCount = 0;
// 第一行:仓库扁平数组
int *warehouses =
parse_numbers(
line1,
&warehouseCount
);
// 第二行:查询数据
int *queryNums =
parse_numbers(
line2,
&queryCount
);
// 第三行:仓库数量
int numOfWarehouse =
atoi(line3);
if (
numOfWarehouse <= 0
|| warehouseCount == 0
) {
printf("\n");
free(line1);
free(line2);
free(line3);
free(warehouses);
free(queryNums);
return 0;
}
/*
* 每个仓库每天固定存储:
*
* inCost、outRevenue、loss
*
* 三个整数。
*
* 因此可以根据数据总长度反推出
* 每个仓库拥有多少天的数据。
*/
int days =
warehouseCount
/ (numOfWarehouse * 3);
/*
* C语言中使用一维数组模拟二维数组。
*
* 原本:
* prefix[w][d]
*
* 转换为:
* prefix[w * cols + d]
*
* cols = days + 1
*/
int cols = days + 1;
int size =
numOfWarehouse * cols;
int *inPrefix =
(int *)calloc(
size,
sizeof(int)
);
int *outPrefix =
(int *)calloc(
size,
sizeof(int)
);
int *lossPrefix =
(int *)calloc(
size,
sizeof(int)
);
// 构造三组前缀和
for (
int w = 0;
w < numOfWarehouse;
w++
) {
for (
int d = 0;
d < days;
d++
) {
/*
* 每个仓库的数据连续排列,
* 每天固定占用三个整数。
*/
int index =
(w * days + d) * 3;
int pos =
w * cols + d;
inPrefix[pos + 1] =
inPrefix[pos]
+ warehouses[index];
outPrefix[pos + 1] =
outPrefix[pos]
+ warehouses[index + 1];
lossPrefix[pos + 1] =
lossPrefix[pos]
+ warehouses[index + 2];
}
}
int first = 1;
// 每三个整数构成一个查询
for (
int i = 0;
i + 2 < queryCount;
i += 3
) {
int warehouseId =
queryNums[i];
int startDay =
queryNums[i + 1];
int endDay =
queryNums[i + 2];
int base =
warehouseId * cols;
/*
* 使用前缀和计算:
*
* [startDay, endDay]
*
* 区间累计值 =
* prefix[endDay + 1]
* -
* prefix[startDay]
*
* 每次查询只需要 O(1) 时间。
*/
int totalIn =
inPrefix[
base + endDay + 1
]
-
inPrefix[
base + startDay
];
int totalOut =
outPrefix[
base + endDay + 1
]
-
outPrefix[
base + startDay
];
int totalLoss =
lossPrefix[
base + endDay + 1
]
-
lossPrefix[
base + startDay
];
// 净利润 = 累计收入 - 累计成本
int netProfit =
totalOut - totalIn;
int totalAmount =
totalIn + totalOut;
int risk = 0;
if (totalAmount != 0) {
/*
* 使用 long long 保存中间乘法结果,
* 防止整数乘法产生溢出。
*
* 将损耗率百分比保留两位以后放大100倍:
*
* 5.00% -> 500
* 5.01% -> 501
*
* 全程使用整数计算实现四舍五入,
* 避免浮点数的精度问题。
*/
long long numerator =
(long long)totalLoss
* 10000LL;
long long roundedRate =
(
numerator * 2LL
+ totalAmount
)
/
(
2LL * totalAmount
);
risk =
roundedRate > 500
? 1
: 0;
}
if (!first) {
printf(" ");
}
first = 0;
printf(
"%d,%d,%d",
netProfit,
totalLoss,
risk
);
}
printf("\n");
// 释放动态内存
free(line1);
free(line2);
free(line3);
free(warehouses);
free(queryNums);
free(inPrefix);
free(outPrefix);
free(lossPrefix);
return 0;
}
九、C++算法源码
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>
using namespace std;
/*
* 将逗号替换为空格,
* 然后利用 stringstream 自动处理一个或多个空格,
* 从而解析出所有整数。
*/
static vector<int> parseNumbers(
string text
) {
replace(
text.begin(),
text.end(),
',',
' '
);
stringstream ss(text);
vector<int> nums;
int x;
while (ss >> x) {
nums.push_back(x);
}
return nums;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
/*
* 与 Java 代码保持一致:
* 固定读取三行。
*/
string line1;
string line2;
string line3;
if (!getline(cin, line1)) {
return 0;
}
if (!getline(cin, line2)) {
return 0;
}
if (!getline(cin, line3)) {
return 0;
}
// 第一行:仓库扁平数组
vector<int> warehouses =
parseNumbers(line1);
// 第二行:所有查询
vector<int> queryNums =
parseNumbers(line2);
// 第三行:仓库数量
int numOfWarehouse =
line3.empty()
? 0
: stoi(line3);
if (
numOfWarehouse <= 0
|| warehouses.empty()
) {
cout << '\n';
return 0;
}
/*
* 每个仓库每天固定包含:
*
* inCost
* outRevenue
* loss
*
* 三个字段。
*
* 所有仓库的天数完全相同,
* 所以可以根据总元素数量反推出天数。
*/
int days =
static_cast<int>(
warehouses.size()
)
/
(
numOfWarehouse * 3
);
/*
* prefix[w][d] 表示:
* 仓库 w 前 d 天的累计数据。
*
* 将第二维长度设置为 days + 1,
* 第0个位置为0,可以统一处理所有查询区间。
*/
vector<vector<int>> inPrefix(
numOfWarehouse,
vector<int>(
days + 1,
0
)
);
vector<vector<int>> outPrefix(
numOfWarehouse,
vector<int>(
days + 1,
0
)
);
vector<vector<int>> lossPrefix(
numOfWarehouse,
vector<int>(
days + 1,
0
)
);
// 构造三组前缀和
for (
int w = 0;
w < numOfWarehouse;
++w
) {
for (
int d = 0;
d < days;
++d
) {
/*
* warehouses 中:
*
* 仓库0的所有天数据在前,
* 仓库1的所有天数据随后,
* ...
*
* 每天固定占用三个位置。
*/
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];
}
}
bool first = true;
/*
* 每三个数字组成一个查询:
*
* warehouseId
* startDay
* endDay
*/
for (
int i = 0;
i + 2
< static_cast<int>(
queryNums.size()
);
i += 3
) {
int warehouseId =
queryNums[i];
int startDay =
queryNums[i + 1];
int endDay =
queryNums[i + 2];
/*
* 使用前缀和计算闭区间:
*
* [startDay, endDay]
*
* 区间和 =
* prefix[endDay + 1]
* -
* prefix[startDay]
*
* 查询时间复杂度为 O(1)。
*/
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) {
/*
* 使用 long long 保存中间计算,
* 避免乘法发生整型溢出。
*
* 损耗率:
*
* totalLoss / totalAmount * 100%
*
* 百分比保留两位小数以后,
* 再扩大100倍:
*
* 5.00% -> 500
* 5.01% -> 501
*
* 用整数运算完成四舍五入,
* 避免浮点精度影响5%的临界判断。
*/
long long numerator =
1LL
* totalLoss
* 10000;
long long roundedRate =
(
numerator * 2
+ totalAmount
)
/
(
2LL
* totalAmount
);
// 严格超过5.00%才触发风控
risk =
roundedRate > 500
? 1
: 0;
}
if (!first) {
cout << ' ';
}
first = false;
cout
<< netProfit
<< ','
<< totalLoss
<< ','
<< risk;
}
cout << '\n';
return 0;
}
🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++ 新系统 200分)
🏆本文收录于,华为OD机试真题(Python/JS/C/C++)
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。

45万+

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



