免单订单统计
问题背景
一个在线商城正在举办促销活动,活动规则是:在任意一秒内,下单时间最早的一个或多个订单可以获得“免单”资格。我们需要根据一批订单的精确下单时间,统计出所有能够获得免单资格的订单总数。
免单规则
免单资格的判断分为两步:
-
按秒分组: 首先,将所有的订单按照它们的下单时间(精确到秒,即
YYYY-MM-DD hh:mm:ss部分)进行分组。 -
寻找最早: 在每一个分组内(即同一秒内的所有订单中),找到那个毫秒值最小的订单。
- 如果该最小毫秒值是唯一的,则只有这一个订单可以免单。
- 如果有多个订单共享这个最小的毫秒值,那么所有这些订单都可以免单。
任务要求
给定一批订单的下单时间记录 orderTime,请根据上述免单规则,计算并返回总共有多少个订单可以获取免单。
输入格式
-
orderTime: 一个字符串列表,每个元素代表一个订单的下单时间。-
1 <= orderTime.length <= 50000 -
时间格式:
"YYYY-MM-DD hh:mm:ss.fff"YYYY-MM-DD hh:mm:ss: 表示年-月-日 时:分:秒,均为合法的时间值。fff: 表示毫秒值,范围在[0, 999]之间。
-
输出格式
- 一个整数,表示可以获取免单的订单总数。
样例说明
样例 1
-
输入:
["2019-01-01 00:00:00.001", "2019-01-01 00:00:00.002", "2019-01-01 00:00:00.003", "2019-01-01 08:59:00.123", "2019-01-01 08:59:00.123", "2018-12-28 13:08:00.999"] -
输出:
4 -
解释:
-
分组
2019-01-01 00:00:00:- 包含的毫秒值:
[001, 002, 003]。 - 该秒内最早的时间是
.001。 - 只有一个订单的毫秒值为
001。 (免单数: 1)
- 包含的毫秒值:
-
分组
2019-01-01 08:59:00:- 包含的毫秒值:
[123, 123]。 - 该秒内最早的时间是
.123。 - 有两个订单的毫秒值都是
123。 (免单数: 2)
- 包含的毫秒值:
-
分组
2018-12-28 13:08:00:- 包含的毫秒值:
[999]。 - 该秒内最早的时间是
.999。 - 只有一个订单。 (免单数: 1)
- 包含的毫秒值:
-
总计:
1 + 2 + 1 = 4个免单订单。
-
样例 2
-
输入:
["2019-01-01 00:00:00.004", "2019-01-01 00:00:00.004", "2019-01-01 00:00:01.006", "2019-01-01 00:00:01.006", "2019-01-01 00:00:01.005"] -
输出:
3 -
解释:
-
分组
2019-01-01 00:00:00:- 包含的毫秒值:
[004, 004]。 - 该秒内最早的时间是
.004。 - 有两个订单的毫秒值都是
004。 (免单数: 2)
- 包含的毫秒值:
-
分组
2019-01-01 00:00:01:- 包含的毫秒值:
[006, 006, 005]。 - 该秒内最早的时间是
.005。 - 只有一个订单的毫秒值为
005。 (免单数: 1)
- 包含的毫秒值:
-
总计:
2 + 1 = 3个免单订单。
-
import java.util.HashMap;
import java.util.Map;
public class Solution {
/**
* 计算可以获取免单的订单数量。
* 免单规则:某一秒内,下单时间最早的一个或多个订单可以免单。
*
* @param orderTime 订单时间的字符串数组,格式为 "YYYY-MM-DD hh:mm:ss.fff"。
* @return 可以获取免单的订单总数。
*/
public int getFreeOrderCount(String[] orderTime) {
// --- 1. 初始化数据结构 ---
// Key: "YYYY-MM-DD hh:mm:ss" 格式的秒级时间字符串。
// Value: 一个长度为2的整型数组,格式为 {记录的该秒内最小的毫秒值, 该最小毫秒值出现的次数}。
Map<String, int[]> secondStats = new HashMap<>();
// --- 2. 遍历所有订单,统计每秒的最早订单信息 ---
for (String timestamp : orderTime) {
// a. 解析时间戳,分割秒和毫秒
// lastIndexOf('.') 确保能正确处理文件名等可能包含点的情况,找到最后一个点
int dotIndex = timestamp.lastIndexOf('.');
if (dotIndex == -1) {
// 如果格式不正确(没有毫秒部分),跳过此条记录
continue;
}
String secondPart = timestamp.substring(0, dotIndex);
int millisecondPart;
try {
millisecondPart = Integer.parseInt(timestamp.substring(dotIndex + 1));
} catch (NumberFormatException e) {
// 如果毫秒部分不是有效数字,跳过此条记录
continue;
}
// b. 更新或插入统计信息
// 检查当前 "秒" 是否已经有记录
if (!secondStats.containsKey(secondPart)) {
// 如果是该秒内遇到的第一个订单,它就是当前最早的。
// 创建新条目并存入 Map。
secondStats.put(secondPart, new int[]{millisecondPart, 1});
} else {
// 如果该秒已经有记录,则进行比较和更新。
int[] stats = secondStats.get(secondPart);
int minMillisSoFar = stats[0]; // 已记录的最小毫秒
if (millisecondPart < minMillisSoFar) {
// 发现了新的、更早的订单。
// 重置该秒的统计信息:更新最小毫秒,并将计数重置为 1。
stats[0] = millisecondPart;
stats[1] = 1;
} else if (millisecondPart == minMillisSoFar) {
// 又一个与最早时间相同的订单,增加计数器。
stats[1]++;
}
// 如果 millisecondPart > minMillisSoFar,则不是最早的订单,无需任何操作。
}
}
// --- 3. 计算总的免单数量 ---
int totalFreeOrders = 0;
// 遍历 map 中所有秒的统计结果
for (int[] stats : secondStats.values()) {
// 将每秒钟最早订单的数量 (存储在 stats[1]) 累加起来
totalFreeOrders += stats[1];
}
return totalFreeOrders;
}
}