← 返回题目列表

分数转小数如何检测循环节?(LeetCode 166)

中等 第 20 / 27 题 更新于 2026/08/01
数学哈希表长除法循环节

简化版

分数转小数本质是模拟竖式除法。整数部分直接相除,小数部分不断让余数乘 10 再除以分母。只要某个余数第二次出现,从第一次出现的位置到当前位置就是循环节,用括号括起来即可。还要处理正负号、0、以及整数溢出,所以计算前转成 long

详细版

String fractionToDecimal(int numerator, int denominator) {
    if (numerator == 0) return "0";
    StringBuilder ans = new StringBuilder();
    long a = numerator, b = denominator;
    if ((a < 0) ^ (b < 0)) ans.append("-");
    a = Math.abs(a);
    b = Math.abs(b);

    ans.append(a / b);
    long rem = a % b;
    if (rem == 0) return ans.toString();
    ans.append(".");

    Map<Long, Integer> seen = new HashMap<>();
    while (rem != 0) {
        if (seen.containsKey(rem)) {
            int pos = seen.get(rem);
            ans.insert(pos, "(");
            ans.append(")");
            break;
        }
        seen.put(rem, ans.length());
        rem *= 10;
        ans.append(rem / b);
        rem %= b;
    }
    return ans.toString();
}

余数决定后续所有小数位,所以记录“余数第一次出现的位置”就能定位循环起点。时间复杂度与循环节长度有关,最多出现 denominator 种余数。

完整版教学

一、为什么这题要模拟竖式除法

十进制小数的生成过程就是竖式除法:先得到整数部分,再用余数继续产生小数位。

1 / 6:
整数部分 0,余数 1
1 * 10 / 6 = 1,余数 4
4 * 10 / 6 = 6,余数 4
余数 4 再次出现,所以 6 循环

小数位不是凭空来的,每一位都由“当前余数乘 10 后除以分母”得到。

二、为什么重复余数代表循环

当某个余数再次出现时,从这一刻开始,后续运算状态与上一次完全相同:同样的余数、同样的分母,所以会生成同样的小数位序列。

当前余数下一位新余数
114
464
464

因此第一次出现该余数的位置就是循环节左括号应该插入的位置。

关键点:不是记录“小数数字是否重复”,而是记录“余数是否重复”。数字重复不一定形成循环,余数重复才意味着后续状态完全复现。

三、哈希表记录什么

哈希表 seen 的 key 是余数,value 是这个余数对应的小数位即将写入的位置,也就是 ans.length()

seen.put(rem, ans.length());
rem *= 10;
ans.append(rem / b);
rem %= b;

如果余数 rem 再出现,就在 seen.get(rem) 位置插入 "(",并在末尾追加 ")"

四、符号与零的处理

如果分子是 0,结果必然是 "0",不需要考虑符号。否则符号由分子和分母是否异号决定:

if ((a < 0) ^ (b < 0)) ans.append("-");

异或表示“只有一个为负”。随后把分子和分母都转为正数继续算。

五、为什么要转成 long

Integer.MIN_VALUE 的绝对值无法用 int 表示:

Integer.MIN_VALUE = -2147483648
abs(-2147483648) 仍可能溢出

所以必须先转 long,再取绝对值。这个边界在整数题里非常常见,面试时主动说出来会显得稳。

六、终止条件与复杂度

循环有两个终止条件:

余数变成 0:有限小数
余数重复:循环小数

分母为 b 时,余数只可能是 0..b-1。如果没有归零,最多经过 b-1 个非零余数就一定重复,所以算法会终止。空间复杂度也是记录这些余数,最坏 O(b)

七、常见误区与追问

  • 误区:记录出现过的小数位而不是余数。 单个数字重复不能确定循环节,状态重复才行。
  • 误区:忘记在余数第一次出现前记录位置。 位置错了,括号会插偏。
  • 误区:直接对 int 取绝对值。 Integer.MIN_VALUE 会溢出,应该先转 long。
  • 追问:为什么余数重复后一定循环? 因为后续计算只由余数和分母决定,状态完全相同。
  • 追问:有限小数什么时候结束? 余数变成 0 时结束。
  • 追问:时间复杂度如何界定? 与产生的小数位数有关,余数最多有 denominator 种。

八、加强记忆

分数转小数就记住“竖式除法 + 余数定位循环”。整数部分直接除,小数部分每轮 rem *= 10,写商,再取余。哈希表不是存数字,而是存余数到答案下标的映射;余数重现,就把括号插回第一次出现的位置。