免单订单统计

65 阅读4分钟

免单订单统计

问题背景

一个在线商城正在举办促销活动,活动规则是:在任意一秒内,下单时间最早的一个或多个订单可以获得“免单”资格。我们需要根据一批订单的精确下单时间,统计出所有能够获得免单资格的订单总数。

免单规则

免单资格的判断分为两步:

  1. 按秒分组: 首先,将所有的订单按照它们的下单时间(精确到秒,即 YYYY-MM-DD hh:mm:ss 部分)进行分组。

  2. 寻找最早: 在每一个分组内(即同一秒内的所有订单中),找到那个毫秒值最小的订单。

    • 如果该最小毫秒值是唯一的,则只有这一个订单可以免单。
    • 如果有多个订单共享这个最小的毫秒值,那么所有这些订单都可以免单

任务要求

给定一批订单的下单时间记录 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

  • 解释:

    1. 分组 2019-01-01 00:00:00:

      • 包含的毫秒值: [001, 002, 003]
      • 该秒内最早的时间是 .001
      • 只有一个订单的毫秒值为 001(免单数: 1)
    2. 分组 2019-01-01 08:59:00:

      • 包含的毫秒值: [123, 123]
      • 该秒内最早的时间是 .123
      • 有两个订单的毫秒值都是 123(免单数: 2)
    3. 分组 2018-12-28 13:08:00:

      • 包含的毫秒值: [999]
      • 该秒内最早的时间是 .999
      • 只有一个订单。 (免单数: 1)
    4. 总计: 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

  • 解释:

    1. 分组 2019-01-01 00:00:00:

      • 包含的毫秒值: [004, 004]
      • 该秒内最早的时间是 .004
      • 有两个订单的毫秒值都是 004(免单数: 2)
    2. 分组 2019-01-01 00:00:01:

      • 包含的毫秒值: [006, 006, 005]
      • 该秒内最早的时间是 .005
      • 只有一个订单的毫秒值为 005(免单数: 1)
    3. 总计: 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;
    }
}