【算法】【学习笔记】数论
🤔这个,这个笔记感觉没什么用啊,因为数论其实就是数学知识,遇到了知道了才懂,没思路的就是不会的。毕竟是数学题。
数论一般就是需要数学知识来解决问题的。常见题型解法有:
- 最大公约数与最小公倍数 (GCD / LCM):欧几里得算法(辗转相除法),将求公约数的时间压缩至 \(O(\log N)\)。
- 质数判定与筛选 (Primes & Sieves):
- 单数判质:利用因子对称性试除到 \(\sqrt{N}\),复杂度 \(O(\sqrt{N})\)。
- 范围筛选(埃氏筛 / 欧拉筛):在 \(O(N)\) 线性时间内批量找出 \(1 \sim N\) 范围内的所有质数。
- 快速幂 (Binary Exponentiation):求解 \(a^b \pmod m\)。将传统循环相乘的 \(O(b)\) 复杂度,借助二进制按位拆分降至 \(O(\log b)\)。
- 扩展欧几里得算法 (ExGCD) 与模逆元:在算法中处理带模运算的除法 \((a / b) \pmod m\) 时,因除法不满足模运算同余律,必须借助费马小定理或 ExGCD 将除法转化为乘法(乘逆元)。
❔题目
1. 找出数组的最大公约数
解题思路
先遍历找到最大值和最小值,然后进行辗转相除法。
实现
function findGCD(nums: number[]): number {
let a = Math.min(...nums), b = Math.max(...nums);
while(b != 0){
let c = a % b;
a = b;
b = c;
}
return a;
};
public class Solution {
private int GCD(int a, int b){
return b == 0 ? a : GCD(b, a % b);
}
public int FindGCD(int[] nums) {
int minn = int.MaxValue, maxn = -1;
int len = nums.Length;
foreach(int a in nums){
minn = Math.Min(minn, a);
maxn = Math.Max(maxn, a);
}
return GCD(minn, maxn);
}
}
class Solution {
public:
int findGCD(std::vector<int>& nums) {
// 一次遍历获取最小值与最大值迭代器
auto [min_it, max_it] = std::minmax_element(nums.begin(), nums.end());
// 直接使用 C++17 标准库的 std::gcd
return std::gcd(*min_it, *max_it);
}
};
复杂度分析
- 时间复杂度:\(O(N + \log(\min(\text{max}, \text{min})))\),其中 \(N\) 是数组长度(用于寻找最值),欧几里得算法的时间复杂度为对数级。
- 空间复杂度: \(O(1)\)
2. n 的第 k 个因子
解题思路
因子成对出现性质:
如果 \(i\) 是 \(n\) 的因子,那么 \(n / i\) 也必然是 \(n\) 的因子。
利用这个性质,所有的因子都对称分布在 \(\sqrt{n}\) 的两边。我们不需要遍历到 \(n\),只需遍历到 \(\sqrt{n}\) 就可以在 \(O(\sqrt{n})\) 时间内拿到所有因子。
核心步骤
- 第一阶段(较小因子 \(1 \sim \sqrt{n}\)):
- 遍历 \(i\) 从 \(1\) 到 \(\lfloor\sqrt{n}\rfloor\)。
- 如果
n % i == 0,说明 \(i\) 是一个较小的因子:- 让 \(k\) 减 1。如果 \(k == 0\),说明第 \(k\) 个因子正好落在较小因子区,直接返回 \(i\)。
- 将 \(i\) 存入动态数组
factors中,备用推导较大的对称因子。
- 第二阶段(较大因子 \(\sqrt{n} \sim n\)):
- 如果遍历完 \(1 \sim \sqrt{n}\) 后 \(k\) 依然大于 0,说明第 \(k\) 个因子落在大于 \(\sqrt{n}\) 的后半段。
- 较大的因子可以通过对
factors数组倒序计算 \(n / \text{factor}\) 依次推导出。 - 特判完全平方数:若 \(n\) 是完全平方数(如 \(n = 16\),\(\sqrt{16} = 4\)),数组末尾的 \(\sqrt{n}\) 的对称点就是自身,不能重复计算,有效较大因子数量需减 1。
为什么?
因为对于完全平方数,\(\sqrt{n}\) 的对称因子就是它自己。如果不减 1,这个中间数就会被重复计算两次。
实现
function kthFactor(n: number, k: number): number {
const factors: number[] = [];
for (let i = 1; i * i <= n; i++) {
if (n % i === 0) {
factors.push(i);
if (--k === 0) return i;
}
}
let sz = factors.length;
if (factors[sz - 1] * factors[sz - 1] === n) {
sz--;
}
if (k <= sz) {
return Math.floor(n / factors[sz - k]);
}
return -1;
}
public class Solution {
public int KthFactor(int n, int k) {
List<int> factors = new List<int>();
for (int i = 1; i * i <= n; i++) {
if (n % i == 0) {
factors.Add(i);
if (--k == 0) return i;
}
}
int sz = factors.Count;
if (factors[sz - 1] * factors[sz - 1] == n) {
sz--;
}
if (k <= sz) {
return n / factors[sz - k];
}
return -1;
}
}
class Solution {
public:
int kthFactor(int n, int k) {
vector<int> factors;
// 1. 扫描 1 到 sqrt(n)
for (int i = 1; i * i <= n; ++i) {
if (n % i == 0) {
factors.push_back(i);
if (--k == 0) return i; // 前半段直接命中
}
}
int sz = factors.size();
// 如果 n 是完全平方数,根节点 sqrt(n) 的对称点就是自身,不能重复算
if (factors.back() * factors.back() == n) {
sz--;
}
// 2. 在较大因子区(后半段)匹配倒数第 k 个对称因子
if (k <= sz) {
return n / factors[sz - k];
}
return -1;
}
};
复杂度分析
- 时间复杂度:\(O(\sqrt{n})\)
内层循环最多执行 \(\sqrt{n}\) 次。 - 空间复杂度:\(O(\sqrt{n})\)
用来存储前半段小因子的数组空间最多只有 \(\sqrt{n}\) 个元素。
3. 质数的最大距离
解题思路
这个老简单了,这个就要做个找质数的函数,然后遍历找最左和最右的质数即可。
实现
function maximumPrimeDifference(nums: number[]): number {
const isPrime = (n: number): boolean => {
if (n < 2) return false;
for (let i = 2; i * i <= n; i++) {
if (n % i === 0) return false;
}
return true;
};
let left = 0;
while (left < nums.length && !isPrime(nums[left])) {
left++;
}
let right = nums.length - 1;
while (right >= 0 && !isPrime(nums[right])) {
right--;
}
return right - left;
}
public class Solution {
public int MaximumPrimeDifference(int[] nums) {
int l = 0;
while(l < nums.Length && !IsPrime(nums[l])){
l++;
}
int r = nums.Length-1;
while(r >= 0 && !IsPrime(nums[r])){
r--;
}
return r - l;
}
private bool IsPrime(int n){
if(n < 2) return false;
for(int i = 2; i * i <= n; i++){
if(n % i == 0) return false;
}
return true;
}
}
class Solution {
public:
int maximumPrimeDifference(vector<int>& nums) {
int l = 0;
while(l < nums.size() && !isPrime(nums[l])){
l++;
}
int r = nums.size()-1;
while(r >= 0 && !isPrime(nums[r])){
r--;
}
return r - l;
}
private:
bool isPrime(int n){
if(n < 2) return false;
for(int i = 2; i * i <= n; i++){
if(n % i == 0) return false;
}
return true;
}
};
复杂度分析
- 时间复杂度: \(O(N)\)
- 空间复杂度: \(O(1)\)
引用
[1] 力扣探索模式
注:本文为个人学习与刷题笔记,部分文本结构与排版格式由 AI 辅助整理。

浙公网安备 33010602011771号