AIGC标识 【算法】【学习笔记】数论

🤔这个,这个笔记感觉没什么用啊,因为数论其实就是数学知识,遇到了知道了才懂,没思路的就是不会的。毕竟是数学题。

数论一般就是需要数学知识来解决问题的。常见题型解法有:

  • 最大公约数与最小公倍数 (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. 第一阶段(较小因子 \(1 \sim \sqrt{n}\)):
    • 遍历 \(i\)\(1\)\(\lfloor\sqrt{n}\rfloor\)
    • 如果 n % i == 0,说明 \(i\) 是一个较小的因子:
      • \(k\) 减 1。如果 \(k == 0\),说明第 \(k\) 个因子正好落在较小因子区,直接返回 \(i\)
      • \(i\) 存入动态数组 factors 中,备用推导较大的对称因子。
  2. 第二阶段(较大因子 \(\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 辅助整理。

posted @ 2026-09-03 23:25  SEHOD  阅读(10)  评论(0)    收藏  举报