一、数学与数论:从素数到快速幂
数学与数论,听起来像是数学课的内容,对吧?但说实话,在算法面试里,这块内容出现的频率相当高。我个人习惯把这类问题归为「套路题」—— 一旦你掌握了几个核心模板,大部分题目都能迎刃而解。
今天我们就来聊聊三个最基础也最常用的数学工具:素数判断、最大公约数、快速幂。我会结合经典例题 Pow(x, n) 和质数计数,带你走一遍实战流程。
1. 素数判断:从暴力到优化
判断一个数是不是素数,这应该是每个程序员都写过的代码。但你真的写对了吗?
基础版:试除法
function isPrime(n) {
if (n < 2) return false;
for (let i = 2; i < n; i++) {
if (n % i === 0) return false;
}
return true;
}
这个版本能跑,但效率太低了。n 很大的时候,循环次数接近 n 次。嗯,这里要注意:其实我们只需要检查到 √n 就够了。
优化版:检查到 √n
function isPrime(n) {
if (n < 2) return false;
if (n === 2) return true;
if (n % 2 === 0) return false;
for (let i = 3; i * i <= n; i += 2) {
if (n % i === 0) return false;
}
return true;
}
为什么可以只检查到 √n?你想想看,如果 n 有一个大于 √n 的因子 a,那么必然存在一个小于 √n 的因子 b = n / a。所以检查到 √n 就够了。
2. 最大公约数:欧几里得算法
求两个数的最大公约数,最经典的方法就是欧几里得算法,也叫辗转相除法。说白了就是一句话:gcd(a, b) = gcd(b, a % b)。
function gcd(a, b) {
while (b !== 0) {
let temp = b;
b = a % b;
a = temp;
}
return a;
}
这个算法的时间复杂度是 O(log min(a, b)),非常快。我记得有一次在写分数化简的模块时,就用到了这个算法,几行代码就搞定了。
3. 快速幂:指数运算的利器
快速幂,顾名思义,就是快速计算 a^n 的方法。常规做法是循环 n 次,时间复杂度 O(n)。但快速幂可以做到 O(log n)。
核心思想是什么?把指数 n 写成二进制形式。比如计算 3^13,13 的二进制是 1101,那么 3^13 = 3^8 * 3^4 * 3^1。
function fastPow(base, exp) {
let result = 1;
while (exp > 0) {
if (exp & 1) { // 如果当前二进制位是1
result *= base;
}
base *= base;
exp >>= 1; // 右移一位
}
return result;
}
这个写法很简洁,但要注意:如果指数很大,结果可能会溢出。实际工程中,我们通常会对结果取模。
4. 经典例题实战
例题一:Pow(x, n)
LeetCode 第 50 题,实现 pow(x, n)。这道题就是快速幂的典型应用,但要注意 n 可能是负数。
function myPow(x, n) {
if (n === 0) return 1;
if (n < 0) {
x = 1 / x;
n = -n;
}
let result = 1;
while (n > 0) {
if (n & 1) result *= x;
x *= x;
n >>= 1;
}
return result;
}
例题二:质数计数
LeetCode 第 204 题,统计小于 n 的质数个数。这道题如果用逐个判断的方法,时间复杂度是 O(n√n),n 大了根本跑不动。
更好的方法是 埃拉托斯特尼筛法,简称埃氏筛。核心思想:从 2 开始,把每个质数的倍数都标记为合数。
function countPrimes(n) {
if (n < 2) return 0;
let isPrime = new Array(n).fill(true);
isPrime[0] = isPrime[1] = false;
for (let i = 2; i * i < n; i++) {
if (isPrime[i]) {
for (let j = i * i; j < n; j += i) {
isPrime[j] = false;
}
}
}
let count = 0;
for (let i = 2; i < n; i++) {
if (isPrime[i]) count++;
}
return count;
}
为什么内层循环从 i*i 开始?因为 i 的较小倍数已经被更小的质数标记过了。这个优化能省掉不少重复操作。
5. 知识体系总览
下面这张图总结了本章的核心知识点和它们之间的关系:
6. 总结与避坑
好了,我们来快速回顾一下今天的内容:
- 素数判断:试除法检查到 √n,批量判断用埃氏筛
- 最大公约数:欧几里得算法,几行代码搞定
- 快速幂:二进制分解,O(log n) 时间
- 经典例题:Pow(x, n) 注意负数处理,质数计数用筛法
数学与数论这块,说白了就是几个固定套路。你只要把今天讲的三个模板练熟,再遇到相关题目,基本都能快速找到思路。下一章我们会继续深入,聊聊排列组合与概率统计,敬请期待。
公众号:蓝海资料掘金营,微信deep3321