目录

题目描述

1492. n 的第 k 个因子

题意分析

给两个正整数 nk,把 n 的所有因子(能整除 n 的正整数)按从小到大排好,返回第 k 个;如果因子总数不足 k 个,返回 -1

「因子」的范围要先钉死:包含 1n 自身n = 7 的因子是 [1, 7] 而不是空集或 [7]。这个定义直接决定了第 1 个因子恒为 1、最后一个因子恒为 n

「从小到大」是排序要求,但它其实是个免费条件——只要按 1, 2, 3, ... 递增的顺序去试除,找到的因子天然就是升序的,完全不需要额外排序。识别出这一点,问题就从「求出所有因子再排序取第 k」简化成「边找边数,数到 k 就停」。

约束里 1 <= k <= n <= 1000n 只有 1000,$O(n)$ 的逐个试除最多 1000 次取模,瞬间完成。但这个范围小得有点刻意——题目下方明确写着一个进阶要求:能否设计时间复杂度小于 $O(n)$ 的算法。面试里问这道题,多半就是奔着这个进阶去的,所以主解法写完之后必须能说出 $O(\sqrt{n})$ 的思路。

边界:n = 1 时因子只有 [1]k = 1 返回 1、k = 2 返回 -1;n 是质数时因子恰好是 [1, n] 两个;n 是完全平方数时,sqrt(n) 这个因子只出现一次(它与自己配对),这一点在 $O(\sqrt{n})$ 解法里是必须单独处理的坑;k 恰好等于因子总数时返回 n 自身。

解法:从小到大枚举因子

核心思路

因子成对出现:若 d 整除 n,则 n / d 也是因子,并且一对中至少有一个不超过 $\sqrt{n}$。因此只需试除到平方根,就能确定全部因子。

小因子按 d 从小到大出现;对应的大因子 n / d 却从大到小出现。为了整体升序,先正向扫描输出小因子,再从平方根向 1 反向扫描,输出对应的大因子。完全平方数的平方根在两边相同,第二趟必须跳过。

k 作为“还需遇到多少个因子”的计数器,每命中一次就减一,归零时立即返回当前因子。计数不变量是:每次检查候选前,k 始终等于距离目标还需遇到的因子个数。第一趟结束时保存整数平方根上界,供第二趟反向扫描。

正确性说明:每个因子都唯一属于“小于等于平方根的一侧”或其互补的大因子;平方根重复项被显式去除,所以不重不漏。第一趟与第二趟的输出顺序分别递增,且所有小因子不大于所有大因子,因此命中顺序就是全体因子的升序,第 k 次命中即为答案。

解题步骤

  • 从 d=1 开始,在 d <= n / d 时试除,避免计算 d * d 可能溢出。
  • 第一趟每遇到小因子就递减 k,并记录最后检查到的 d 作为平方根上界。
  • 从该上界反向扫描;若 d 整除 n 且 d != n / d,则互补因子 n / d 是尚未输出的大因子。
  • 第 k 次命中立即返回;两趟结束仍未命中则返回 -1。

n = 12 的两趟顺序是 1,2,34,6,12k = 3 返回 3。n = 16 时第二趟跳过重复的 4,因子序列为 1,2,4,8,16。质数的第二个因子是自身;k 超过因子总数时返回 -1。

代码实现

class Solution {
    public int kthFactor(int n, int k) {
        int limit = 0;
        for (int factor = 1; factor <= n / factor; factor++) {
            limit = factor;
            if (n % factor == 0 && --k == 0) {
                return factor;
            }
        }

        for (int factor = limit; factor >= 1; factor--) {
            if (n % factor != 0 || factor == n / factor) {
                continue;
            }
            if (--k == 0) {
                return n / factor;
            }
        }
        return -1;
    }
}
func kthFactor(n int, k int) int {
	limit := 0
	for factor := 1; factor <= n/factor; factor++ {
		limit = factor
		if n%factor == 0 {
			k--
			if k == 0 {
				return factor
			}
		}
	}

	for factor := limit; factor >= 1; factor-- {
		if n%factor != 0 || factor == n/factor {
			continue
		}
		k--
		if k == 0 {
			return n / factor
		}
	}
	return -1
}

复杂度分析

  • 时间复杂度:$O(\sqrt{n})$。正反两趟都只扫描到平方根。
  • 空间复杂度:$O(1)$。不保存因子列表。

关键点总结

  • 因子配对把枚举上界从 n 降到平方根。
  • 小因子正序、大因子需按小因子反序输出,才能得到整体升序。
  • 完全平方数的平方根只能计数一次。
  • factor <= n / factor 代替 factor * factor <= n,避免乘法溢出。

易错点总结

  • 第二趟仍正向扫描n = 12 会按 12,6,4 输出大因子,顺序错误。
  • 平方根重复计数n = 16 会得到 1,2,4,4,8,16,使 k = 4 错误返回 4 而不是 8。
  • 第一趟条件写成 factor < n / factor:会漏掉完全平方数的平方根。
  • 命中前先判断 k:会产生一位偏移;应在确认是因子后递减,再判断是否归零。
  • 循环完返回 0:题目规定因子数量不足时返回 -1。
  • 用整数平方根上界却从 limit - 1 回扫:非完全平方数可能漏掉最接近平方根的小因子对应的大因子。

相似题目

题目 难度 考察点
507. 完美数 简单 要求所有真因子之和,正是配对枚举到 $\sqrt{n}$ 的直接应用,同样要处理平方数
204. 计数质数 中等 统计范围内质数个数,用埃氏筛批量标记合数而非逐个试除
728. 自除数 简单 判定条件转到「每一位数字都能整除自身」,重点在逐位拆解与 0 的排除
172. 阶乘后的零 中等 靠因子 5 的个数直接推导结果,考察把计数问题转成数学公式而非枚举
69. x 的平方根 简单 同样围绕 $\sqrt{n}$ 展开,但用二分或牛顿迭代逼近而非枚举
367. 有效的完全平方数 简单 判定是否存在整数平方根,可用来练习本题进阶里 d * d == n 的边界判断