SWE / OA · HackerRank OA

Stripe 部署窗口 OA:Allowed 减 Freeze、跨周时区与提前量

用 10080 分钟半开区间统一 allowed/freeze,再转换时区并筛最早可部署、最短持续时间和前 k 个窗口。

题目重点:用 10080 分钟半开区间统一 allowed/freeze,再转换时区并筛最早可部署、最短持续时间和前 k 个窗口。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 输入每条本地窗口先满足 0≤start<end≤10080;跨周仅可能由时区平移产生。训练版将跨周转换后的窗口拆为本周 [0,10080) 内两段。
  • Part 2 的 utc_now 为当前周内分钟,训练版只返回本周余下窗口;若题目要求跨未来多周,需把周期窗口展开到绝对时间线,不能按本周截断。
  • lead_time、min_continuous_minutes、k 非负;在 utc_now+lead 之前的窗口被截去左边,截后长度再和 minimum 比。相邻结果合并为一段。

解题路线

  1. 把 allowed 与 freeze 各做区间并集;可用 sweep line 在每个端点维护两类 active count,只有 allowed>0 且 freeze=0 的片段可部署。半开区间使某段的 end 与下一段 start 接触时没有重叠或一分空隙。
  2. 时区 offset 的符号采用 UTC=local−offset;转换后先 modulo 10080,再把超过周末的段切成 [start,10080) 与 [0,end−10080)。不能只对两个端点各自取模然后当作 start<end。
  3. 在最终 UTC 窗口上与 [utc_now+lead,10080) 求交,再丢弃短于 minimum 的连续段,按起点升序取前 k 个。若业务想要可开始时刻而非整个可用区间,输出合同需改。
  4. 训练代码利用一周只有 10080 分钟,使用两组差分数组逐分钟扫描,天然合并相邻分钟;更大时间域改为端点排序 O(n log n)。
  5. 测试 freeze 完全覆盖、两个 allowed 相邻、两个 freeze 重叠、跨周折返、threshold 落段中、最短长度恰边界、k=0 以及允许/冻结同点开始结束。

手动走一遍

题目样例 allowed [540,600)、freeze [570,585) 得 [540,570)、[585,600)。时区例 offset=-480,local allowed [540,600) 变 UTC [1020,1080),freeze [550,565) 变 [1030,1045),utc_now=1020、lead=0、minimum=10 时返回 [1020,1030) 和 [1045,1080)。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

from __future__ import annotations


WEEK = 7 * 24 * 60


def _utc_segments(start: int, end: int, offset: int) -> list[tuple[int, int]]:
    if not 0 <= start < end <= WEEK:
        raise ValueError("expected a nonempty local half-open interval in one week")
    first = (start - offset) % WEEK
    length = end - start
    if first + length <= WEEK:
        return [(first, first + length)]
    return [(first, WEEK), (0, first + length - WEEK)]


def deployment_windows(
    rules: list[tuple[int, int, str, int]],
    *,
    utc_now: int = 0,
    lead_minutes: int = 0,
    min_duration: int = 1,
    limit: int | None = None,
) -> list[tuple[int, int]]:
    """Training contract: return windows only in the current UTC week.

    Rule is (local_start, local_end, 'allowed'|'freeze', utc_offset_minutes).
    Part 1 uses offset 0 and defaults for the other arguments.
    """
    if lead_minutes < 0 or min_duration < 0 or (limit is not None and limit < 0):
        raise ValueError("lead, minimum duration and limit must be nonnegative")
    if limit == 0:
        return []
    allowed_diff = [0] * (WEEK + 1)
    freeze_diff = [0] * (WEEK + 1)
    for start, end, kind, offset in rules:
        if kind not in ("allowed", "freeze"):
            raise ValueError("rule kind must be allowed or freeze")
        diff = allowed_diff if kind == "allowed" else freeze_diff
        for lo, hi in _utc_segments(start, end, offset):
            diff[lo] += 1
            diff[hi] -= 1

    earliest = max(0, utc_now + lead_minutes)
    active_allowed = active_freeze = 0
    start = None
    answer: list[tuple[int, int]] = []
    for minute in range(WEEK + 1):
        active_allowed += allowed_diff[minute]
        active_freeze += freeze_diff[minute]
        usable = minute < WEEK and minute >= earliest and active_allowed > 0 and active_freeze == 0
        if usable and start is None:
            start = minute
        elif not usable and start is not None:
            if minute - start >= min_duration:
                answer.append((start, minute))
                if limit is not None and len(answer) >= limit:
                    return answer
            start = None
    return answer

复杂度

训练版固定周长 W=10080、R 条规则,区间拆分与标记 O(R),扫描 O(W),空间 O(W);通用端点 sweep O(R log R) 时间、O(R) 空间。跨周扩展到 H 周会增加 O(HR) 个周期实例或按周期查询。

易错点与追问

容易失分的边界

  • 使用闭区间把 end 那一分钟也算可部署。
  • 时区 offset=-480 时写成 UTC=local−480,方向相反。
  • 只对 start/end 各取模,不拆跨周窗口。
  • 先按最短长度过滤再剪掉 lead 前半段,留下实际不足时长的窗口。
  • 把‘接下来 k’擅自解释为无限重复周而不注明训练边界。

继续深挖

  • 如果 utc_now=周日且最早时刻落下周,如何用绝对 UTC 时间展开周期窗口?
  • 多地时区有 DST 变化时固定 offset 还能代表当地周吗?
  • 动态增加/删除 allowed 和 freeze,如何维护可部署段?
✓已复制微信号!