目录

题目描述

1344. 时钟指针的夹角

题意分析

给定 hourminutes 两个整数,表示一个 12 小时制时钟上的时刻,要返回时针与分针之间较小的那个夹角,单位是度,允许 $10^{-5}$ 的误差。

题目要求「较小的夹角」,这是第一个必须抓住的点。两根指针把整个表盘分成两段弧,度数之和恒为 360°,所以答案永远落在 [0°, 180°] 区间内,超过 180° 的那一段必须换成它的补角。

第二个必须抓住的点藏在物理常识里:时针不是每小时跳一格,而是随着分钟连续移动的。3 点半时时针并不指着 3,而是在 3 和 4 的正中间。把时针当成整点位置来算是这道题最主要的错误来源,也正是它被定为「中等」而不是「简单」的唯一原因。

约束里 1 <= hour <= 120 <= minutes <= 59。注意 hour 的上界是 12 而不是 11,所以会出现 hour = 12 的输入,它在角度上等价于 0 点,需要折算。

输入规模是常数,没有任何数据结构或遍历的余地,这是一道纯粹的公式推导题——「算法」全部体现在把物理运动翻译成算式的过程里。

边界要留意四点:hour = 12 要折成 0;分针可能反超时针,两个角度谁大谁小不确定,所以要取绝对值;夹角可能恰好是 0°(12:00)或 180°(6:00),都是合法答案;返回值是浮点数,因为 0.5°/分钟的时针速度会产生半度的小数。

解法:角度计算

核心思路

先把表盘建成一个统一的坐标系:以 12 点方向为 0°,顺时针为正方向,一整圈是 360°。这样每根指针在任一时刻都对应一个 [0°, 360°) 的绝对角度,两者相减就是夹角——这一步「统一基准」是把物理问题代数化的关键,没有它就只能凭直觉数格子。

分针的角度最好算。它走完一圈 60 分钟对应 360°,所以速度是 360 / 60 = 6 度每分钟,且不受小时数影响。于是分针角度是 6 × minutes

时针要拆成两部分。整点部分:走完一圈 12 小时对应 360°,所以每小时 360 / 12 = 30 度,贡献 30 × (hour mod 12)。连续部分:时针在一小时内均匀走完这 30°,而这一小时有 60 分钟,所以速度是 30 / 60 = 0.5 度每分钟,贡献 0.5 × minutes。合起来时针角度是 30 × (hour mod 12) + 0.5 × minutes

hour mod 12 把输入归一化到表盘的 0..11:12 点与 0 点方向相同,12 mod 12 = 0。即使多出的一整圈在本题最后也可能被补角消掉,先归一化仍能保证两根指针的绝对角都落在同一标准区间,推导和中间量更清楚。

两个角度都确定后,令 diff = |时针角度 - 分针角度|。取绝对值是必要的:谁在前谁在后随时刻变化,比如 3:00 时时针 90°、分针 0°,而 3:30 时时针 105°、分针 180°,正负号会翻转。

此时 diff 落在 [0°, 360°) 内,它是两根指针沿某一方向的夹角。另一个方向的夹角是 360 - diff,题目要较小的那个,所以答案是 min(diff, 360 - diff)。这一步的不变量是:两段弧之和恒为 360°,取较小者必然落在 [0°, 180°]

正确性来自两个事实:匀速运动公式精确给出了两根指针相对 12 点方向的角度;圆上连接这两个方向的两段弧长度分别是 diff360 - diff。因此取二者较小值,恰好就是题目要求的夹角。

直接用浮点数最自然,因为时针每分钟移动 0.5°,奇数分钟会产生半度。也可以把所有角度先放大 2 倍用整数计算、最后除以 2.0,但本题没有精度累积,double / float64 更简洁。

解题步骤

  • 先算分针角度 6.0 * minutes 系数 6 来自「一圈 360° ÷ 60 分钟」。这里写成 6.0 是为了让角度类型一眼可见;由于乘积本身是整数,写 6 * minutes 后再赋给浮点变量也不会损失精度。
  • 再算时针角度 30.0 * (hour % 12) + 0.5 * minutes 前项系数 30 来自「一圈 360° ÷ 12 小时」,后项系数 0.5 来自「每小时 30° ÷ 60 分钟」。这个加法项是全题的核心:它表达了时针的连续移动,漏掉它会让 3:30 这类时刻算出完全错误的结果。
  • 对小时数取模 12。 hour 的取值范围含 12,而表盘上 12 点就是 0 点。取模后 hour % 12 落在 [0, 11],与「30° 一格」的编号一一对应。
  • 计算 diff = |hourAngle - minuteAngle| 取绝对值是因为两根指针的先后关系不固定,直接相减会出现负值,导致后面的 min 比较把 360 - diff 这个大于 360 的数错当成候选。
  • 返回 min(diff, 360.0 - diff) 这一步把「沿某方向的夹角」修正为「较小夹角」。判据很直观:diff 超过 180° 时,360 - diff 必然小于 180°,反之则 diff 本身更小;恰好 180° 时两者相等,取谁都对。
  • double / float64 保存角度。 0.5 * minutes 可能产生半度;若改用整数,就必须统一保存两倍角度,不能在中途做整数除法。

hour = 3minutes = 30 走一遍(正确答案是 75.0)。

分针角度:6.0 × 30 = 180.0,分针指向表盘正下方的 6 点位置,符合直觉。

时针角度:整点部分 30.0 × (3 % 12) = 90.0,连续部分 0.5 × 30 = 15.0,合计 105.0。105° 落在 3 点(90°)与 4 点(120°)的正中间,正是「三点半时针在 3 和 4 中间」的直观描述。这一步如果漏掉连续部分,时针会停在 90°。

diff = |105.0 - 180.0| = 75.0。这里时针角度小于分针角度,减出来是负的 -75,绝对值把它扳正——这正是必须取绝对值的实例。

min(75.0, 360.0 - 75.0) = min(75.0, 285.0) = 75.0。返回 75.0,与预期一致。若漏掉时针的连续移动,会得到 |90 - 180| = 90,偏差整整 15°。

再看三个边界用例。hour = 12minutes = 0 时两角都是 0°,答案为 0;hour = 12minutes = 30 时两角分别为 15° 和 180°,答案为 165°,说明 12 必须按表盘位置归一化为 0。

hour = 3minutes = 15:分针 6 × 15 = 90°,时针 90 + 0.5 × 15 = 97.5°diff = 7.5,返回 7.5。这个 .5 就是必须用浮点数的直接证据——用 int 会得到 7 甚至 0。

hour = 6minutes = 0:分针 0°,时针 180°,diff = 180min(180, 180) = 180,两针正对,返回 180.0,是答案的上界。

代码实现

class Solution {
    public double angleClock(int hour, int minutes) {
        // 分针每分钟走 360 / 60 = 6 度。
        double minuteAngle = 6.0 * minutes;
        // 时针每小时走 30 度,且在一小时内以 0.5 度每分钟连续推进。
        double hourAngle = 30.0 * (hour % 12) + 0.5 * minutes;
        // 谁在前不确定,取绝对值。
        double diff = Math.abs(hourAngle - minuteAngle);
        // 两段弧之和为 360,取较小的那段。
        return Math.min(diff, 360.0 - diff);
    }
}
import "math"

func angleClock(hour int, minutes int) float64 {
	minuteAngle := 6.0 * float64(minutes)
	hourAngle := 30.0*float64(hour%12) + 0.5*float64(minutes)
	diff := math.Abs(hourAngle - minuteAngle)
	return math.Min(diff, 360.0-diff)
}

复杂度分析

  • 时间复杂度:$O(1)$,全程只有固定的几次乘法、加法、取模、取绝对值和一次比较,运算次数与输入数值大小无关,没有任何循环或递归。
  • 空间复杂度:$O(1)$,只用了 minuteAnglehourAnglediff 三个浮点标量,不随输入变化。

关键点总结

  • 把物理运动拆成「匀速 × 时间」再求和:时针角度 = 整点贡献 + 分钟贡献,两项分别由 30°/小时 和 0.5°/分钟 两个速率导出。凡是多个部件按不同速率联动的题(钟表、追及、传送带),都可以套这个「各自定速率、统一定基准」的框架。
  • 圆周上的夹角要取补角修正:任意两个方向把圆分成两段,和为 360°,「较小夹角」就是 min(d, 360 - d)。这条在几何题、方向题里反复出现,记住比现推快。
  • 周期量先取模归位hour % 12 把 12 点折回 0 点,使两个绝对角都处于同一圈内,补角公式的前提清晰可见。
  • 半度不能被整数除法截断:直接用浮点数最简洁;若面试官要求避免浮点中间量,就统一计算两倍角度,最后除以 2.0。
  • 面试视角:这题代码只有四行,面试官真正在看的是你会不会主动指出「时针是连续移动的」。开口第一句就该说「三点半的时针在 3 和 4 中间,所以时针角度要加上 0.5 × minutes」,然后再补「答案取较小夹角,所以最后要和补角比一次」「hour = 12 要取模归零」。这三句说完基本就满分了。反过来,如果上来就写公式而不解释 0.5 的来历,很容易被追问到卡壳。

易错点总结

  • 错误写法:时针角度只写 30 * (hour % 12),漏掉 0.5 * minutes → 用例 hour = 3, minutes = 30 会算出 |90 - 180| = 90,而正确答案是 75,偏差 15°;所有非整点时刻都会出错。
  • 错误写法:不取绝对值,直接用 hourAngle - minuteAngle → 用例 hour = 3, minutes = 30 得到 -75,随后 min(-75, 435) 返回 -75,出现负角度。
  • 错误写法:省掉 min(diff, 360 - diff),直接返回 diff → 用例 hour = 12, minutes = 30diff = |15 - 180| = 165 恰好正确,但用例 hour = 1, minutes = 57diff = |58.5 - 342| = 283.5,超过 180°,正确答案应是 76.5。
  • 错误写法:全程使用 int 类型,写成 30 * (hour % 12) + minutes / 2 → 用例 hour = 3, minutes = 1515 / 2 整除为 7,时针角度算成 97 而非 97.5,返回 7 而正确答案是 7.5,超出 $10^{-5}$ 的误差容忍。
  • 错误写法:分针角度写成整数表达式 minutes / 60 * 360minutes < 60 时前半段整除恒为 0,分针被错误地固定在 12 点方向。
  • 错误写法:时针分钟贡献用整数表达式 minutes * 30 / 60 → 用例 minutes = 15 得到 7 而不是 7.5,最终夹角误差为 0.5°;应使用 0.5 * minutes 或两倍角度。
  • 错误写法:把时针速度记成 0.5 度每小时或 6 度每分钟 → 用例 hour = 3, minutes = 30 中若时针用 6 度每分钟,角度变成 90 + 180 = 270diff = 90,答案错得离谱;速率一定要从「30° 一格 ÷ 60 分钟」现推。
  • 错误写法:认为答案一定小于 180 而写成 diff % 180 → 用例 hour = 6, minutes = 0diff = 180,取模后变成 0,而正确答案就是 180。
  • 错误写法:把「较小夹角」理解成「顺时针从时针到分针」的角 → 用例 hour = 3, minutes = 0 中顺时针从时针 90° 到分针 0° 是 270°,而正确答案是 90,方向性的定义在本题里是多余且有害的。

相似题目

题目 难度 考察点
539. 最小时间差 中等 同样是环形量取最小差,排序后还要额外比较首尾跨越午夜的一对
LCR 035. 最小时间差 中等 与 539 同题,适合再练一遍「化为分钟数 + 环形首尾」的套路
949. 给定数字能组成的最大时间 中等 时钟主题但转为枚举全排列并校验合法性,考的是边界判定而非公式
1360. 日期之间隔几天 简单 时间换算题,闰年规则是主要坑点,与本题的「12 点折 0 点」同属归一化思路
1041. 困于环中的机器人 中等 方向用 90° 的模 4 周期表示,靠一轮后是否回到原点或转向判断是否有界
874. 模拟行走机器人 中等 方向数组配合模 4 转向,是把「角度」离散化成四个方向的典型简化
7. 整数反转 中等 纯算术推导,核心在溢出判断而非公式本身
9. 回文数 简单 只用整数运算反转一半数字,考的是避免转字符串的数学技巧
172. 阶乘后的零 中等 把「数零的个数」推导成统计因子 5 的个数,同样是把现象翻译成算式
258. 各位相加 简单 数根公式 1 + (n-1) % 9,与本题一样能把过程压成一行常数运算
326. 3 的幂 简单 可用取模循环,也可用「最大 3 的幂能否整除」的常数解,体现数学化的收益
593. 有效的正方形 中等 平面几何判定,靠边长集合的多重集比较避开浮点误差
1071. 字符串的最大公因子 简单 把字符串周期性问题化归为长度的最大公约数,同属「找到隐藏的数学量」
412. Fizz Buzz 简单 周期性判断的入门题,与本题的模运算归位思路一脉相承