Skip to content

本文转载自力扣讨论区,作者:灵茶山艾府(endlesscheng)。 原文链接:https://leetcode.cn/circle/discuss/IYT3ss/


数学题单 数学题目 力扣数学题单 leetcode数学 数论 组合 博弈 几何 随机 灵茶山艾府 灵神 灵神题单

图:暴力?NO!数学做法,降维打击!

前言 ​

本文全面整理了力扣上的数学相关题目,其中数论和组合数学的题目比较多。

部分题目(尤其是组合数学)会涉及到取模,我写了一篇详细的取模教程,请看 模运算的世界:当加减乘除遇上取模。

一、数论 ​

§1.1 判断质数 ​

如何判断一个正整数 n 是不是质数?

只需判断 n 能否能被 n 以内的某个大于 1 的整数整除,如果不能则说明 n 是质数。为什么?

反证法:如果 n 不能被 n 以内的大于 1 的整数整除,但可以被大于 n 的整数 d (d<n) 整除,那么必然还有一个数 nd 也能整除 n。但是 nd<nn=n,说明存在一个 n 以内的大于 1 的整数能整除 n,矛盾。

注意 1 不是质数。

模板:

py
# 时间复杂度 O(sqrt(n))
def is_prime(n: int) -> bool:
    for i in range(2, isqrt(n) + 1):
        if n % i == 0:
            return False
    return n >= 2  # 1 不是质数
java
class Solution {
    // 时间复杂度 O(sqrt(n))
    private boolean isPrime(int n) {
        for (int i = 2; i * i <= n; i++) {
            if (n % i == 0) {
                return false;
            }
        }
        return n >= 2; // 1 不是质数
    }
}
cpp
// 时间复杂度 O(sqrt(n))
bool is_prime(int n) {
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            return false;
        }
    }
    return n >= 2; // 1 不是质数
}
go
// 时间复杂度 O(sqrt(n))
func isPrime(n int) bool {
    for i := 2; i*i <= n; i++ {
        if n%i == 0 {
            return false
        }
    }
    return n >= 2 // 1 不是质数
}

更快的模板(写法二)

§1.2 预处理质数(筛质数) ​

模板(埃氏筛):

py
# 时间复杂度 O(MX * log log MX)
MX = 1_000_001
is_prime = [False] * 2 + [True] * (MX - 2)  # 0 和 1 不是质数
primes = []
for i in range(2, MX):
    if is_prime[i]:
        primes.append(i)
        for j in range(i * i, MX, i):
            is_prime[j] = False  # j 是质数 i 的倍数
java
// 时间复杂度 O(MX * log log MX)
class Solution {
    private static final int MX = 1_000_001;
    private static final boolean[] isPrime = new boolean[MX];
    private static final List<Integer> primes = new ArrayList<>();
    private static boolean initialized = false;

    // 这样写比 static block 快
    public Solution() {
        if (initialized) {
            return;
        }
        initialized = true;

        Arrays.fill(isPrime, true);
        isPrime[0] = isPrime[1] = false; // 0 和 1 不是质数
        for (int i = 2; i < MX; i++) {
            if (isPrime[i]) {
                primes.add(i);
                for (long j = (long) i * i; j < MX; j += i) {
                    isPrime[(int) j] = false; // j 是质数 i 的倍数
                }
            }
        }
    }

    public int solve(int[] nums) {
        
    }
}
cpp
// 时间复杂度 O(MX * log log MX)
constexpr int MX = 1'000'001;
bool is_prime[MX];
vector<int> primes;

auto init = [] {
    ranges::fill(is_prime, true);
    is_prime[0] = is_prime[1] = false; // 0 和 1 不是质数
    for (int i = 2; i < MX; i++) {
        if (is_prime[i]) {
            primes.push_back(i);
            for (long long j = 1LL * i * i; j < MX; j += i) {
                is_prime[j] = false; // j 是质数 i 的倍数
            }
        }
    }
    return 0;
}();
go
// 时间复杂度 O(mx * log log mx)
const mx = 1_000_001

var np = [mx]bool{true, true} // 0 和 1 不是质数
var primes []int

func init() {
    for i := 2; i < mx; i++ {
        if !np[i] {
            primes = append(primes, i)
            for j := i * i; j < mx; j += i {
                np[j] = true
            }
        }
    }
}

§1.3 质因数分解 ​

模板一:预处理每个数的所有不同质因子。原理同埃氏筛。

py
MX = 1_000_001
prime_factors = [[] for _ in range(MX)]
for i in range(2, MX):
    if not prime_factors[i]:  # i 是质数
        for j in range(i, MX, i):  # i 的倍数 j 有质因子 i
            prime_factors[j].append(i)
java
class Solution {
    private static final int MX = 1_000_001;
    private static final List<Integer>[] primeFactors = new ArrayList[MX];
    private static boolean initialized = false;

    // 这样写比 static block 快
    public Solution() {
        if (initialized) {
            return;
        }
        initialized = true;

        Arrays.setAll(primeFactors, _ -> new ArrayList<>());
        for (int i = 2; i < MX; i++) {
            if (primeFactors[i].isEmpty()) { // i 是质数
                for (int j = i; j < MX; j += i) { // i 的倍数 j 有质因子 i
                    primeFactors[j].add(i);
                }
            }
        }
    }

    public int solve(int[] nums) {
        
    }
}
cpp
constexpr int MX = 1'000'001;
vector<int> prime_factors[MX];

int init = [] {
    for (int i = 2; i < MX; i++) {
        if (prime_factors[i].empty()) { // i 是质数
            for (int j = i; j < MX; j += i) { // i 的倍数 j 有质因子 i
                prime_factors[j].push_back(i);
            }
        }
    }
    return 0;
}();
go
const mx = 1_000_001

var primeFactors = [mx][]int{}

func init() {
	for i := 2; i < mx; i++ {
		if primeFactors[i] == nil { // i 是质数
			for j := i; j < mx; j += i { // i 的倍数 j 有质因子 i
				primeFactors[j] = append(primeFactors[j], i)
			}
		}
	}
}

模板二:预处理 x 的最小质因子 LPF(x),从而做到 O(log⁡x) 分解 x,求出 x 的质因子及其出现次数。

py
MX = 1_000_001
lpf = [0] * MX
for i in range(2, MX):
    if lpf[i] == 0:  # i 是质数
        for j in range(i, MX, i):
            if lpf[j] == 0:  # 首次访问 j
                lpf[j] = i

# 质因数分解
# 例如 prime_factorization(45) = [(3, 2), (5, 1)],表示 45 = 3**2 * 5**1
# 时间复杂度 O(log x)
def prime_factorization(x: int) -> List[Tuple[int, int]]:
    res = []
    while x > 1:
        p = lpf[x]
        e = 1
        x //= p
        while x % p == 0:
            e += 1
            x //= p
        res.append((p, e))
    return res
java
class Solution {
    private static final int MX = 1_000_001;
    private static final int[] lpf = new int[MX];
    private static boolean initialized = false;

    // 这样写比 static block 快
    public Solution() {
        if (initialized) {
            return;
        }
        initialized = true;

        for (int i = 2; i < MX; i++) {
            if (lpf[i] == 0) { // i 是质数
                for (int j = i; j < MX; j += i) {
                    if (lpf[j] == 0) { // 首次访问 j
                        lpf[j] = i;
                    }
                }
            }
        }
    }

    // 质因数分解
    // 例如 primeFactorization(45) = [[3, 2], [5, 1]],表示 45 = 3^2 * 5^1
    // 时间复杂度 O(log x)
    private List<int[]> primeFactorization(int x) {
        List<int[]> res = new ArrayList<>();
        while (x > 1) {
            int p = lpf[x];
            int e = 1;
            for (x /= p; x % p == 0; x /= p) {
                e++;
            }
            res.add(new int[]{p, e});
        }
        return res;
    }

    public int solve(int[] nums) {
        
    }
}
cpp
constexpr int MX = 1'000'001;
int lpf[MX];

int init = [] {
    for (int i = 2; i < MX; i++) {
        if (lpf[i] == 0) { // i 是质数
            for (int j = i; j < MX; j += i) {
                if (lpf[j] == 0) { // 首次访问 j
                    lpf[j] = i;
                }
            }
        }
    }
    return 0;
}();

// 质因数分解
// 例如 prime_factorization(45) = {{3, 2}, {5, 1}},表示 45 = 3^2 * 5^1
// 时间复杂度 O(log x)
vector<pair<int, int>> prime_factorization(int x) {
    vector<pair<int, int>> res;
    while (x > 1) {
        int p = lpf[x];
        int e = 1;
        for (x /= p; x % p == 0; x /= p) {
            e++;
        }
        res.emplace_back(p, e);
    }
    return res;
}
go
const mx = 1_000_001

var lpf = [mx]int{}

func init() {
	for i := 2; i < mx; i++ {
		if lpf[i] == 0 { // i 是质数
			for j := i; j < mx; j += i {
				if lpf[j] == 0 { // 首次访问 j
					lpf[j] = i
				}
			}
		}
	}
}

// 质因数分解
// 例如 primeFactorization(45) = [[3, 2], [5, 1]],表示 45 = 3^2 * 5^1
// 时间复杂度 O(log x)
func primeFactorization(x int) (res [][2]int) {
	for x > 1 {
		p := lpf[x]
		e := 1
		for x /= p; x%p == 0; x /= p {
			e++
		}
		res = append(res, [2]int{p, e})
	}
	return
}

§1.4 阶乘分解 ​

§1.5 因子 ​

注:预处理质因子的模板在「§1.3 质因数分解」中。

模板(预处理每个数的所有因子):

py
# 预处理每个数的因子
MX = 1_000_001  # **根据题目调整**
divisors = [[] for _ in range(MX)]
for i in range(1, MX):
    for j in range(i, MX, i):  # 枚举 i 的倍数 j
        divisors[j].append(i)  # i 是 j 的因子
java
class Solution {
    private static final int MX = 1_000_001; // **根据题目调整**
    private static final List<Integer>[] divisors = new ArrayList[MX];
    private static boolean initialized = false;

    // 这样写比 static block 快
    public Solution() {
        if (initialized) {
            return;
        }
        initialized = true;

        Arrays.setAll(divisors, _ -> new ArrayList<>());
        for (int i = 1; i < MX; i++) {
            for (int j = i; j < MX; j += i) { // 枚举 i 的倍数 j
                divisors[j].add(i); // i 是 j 的因子
            }
        }
    }

    public int solve(int[] nums) {
        
    }
}
cpp
constexpr int MX = 1'000'001; // **根据题目调整**
vector<int> divisors[MX];

int init = [] {
    for (int i = 1; i < MX; i++) {
        for (int j = i; j < MX; j += i) { // 枚举 i 的倍数 j
            divisors[j].push_back(i); // i 是 j 的因子
        }
    }
    return 0;
}();
go
const mx = 1_000_001 // **根据题目调整**

var divisors [mx][]int // 如果 mx 很大可能会 MLE,可以改成 int32

func init() {
	for i := 1; i < mx; i++ {
		for j := i; j < mx; j += i { // 枚举 i 的倍数 j
			divisors[j] = append(divisors[j], i) // i 是 j 的因子
		}
	}
}

§1.6 最大公约数(GCD) ​

部分语言的标准库没有 GCD 和 LCM,需要手写。推荐写迭代,比递归快一点。

java
class Solution {
    private long gcd(long a, long b) {
        while (a != 0) {
            long tmp = a;
            a = b % a;
            b = tmp;
        }
        return b;
    }

    // 推荐先除后乘,尽量避免溢出
    private long lcm(long a, long b) {
        return a / gcd(a, b) * b;
    }
}
go
func gcd(a, b int) int {
    for a != 0 {
        a, b = b%a, a
    }
    return b
}

// 推荐先除后乘,尽量避免溢出
func lcm(a, b int) int {
    return a / gcd(a, b) * b
}

扩展:

§1.7 最小公倍数(LCM) ​

§1.8 互质 ​

§1.9 同余 ​

§1.10 数论分块 ​

§1.11 莫比乌斯函数 ​

数论中的容斥原理。

§1.12 其他 ​

二、组合数学 ​

§2.1 乘法原理 ​

§2.2 组合计数 ​

预处理阶乘及其逆元后,可以 O(1) 计算组合数,模板代码如下。原理见 模运算的世界:当加减乘除遇上取模。

py
MOD = 1_000_000_007
MX = 100_001  # 根据题目数据范围修改

fac = [0] * MX  # fac[i] = i!
fac[0] = 1
for i in range(1, MX):
    fac[i] = fac[i - 1] * i % MOD

inv_f = [0] * MX  # inv_f[i] = i!^-1
inv_f[-1] = pow(fac[-1], -1, MOD)
for i in range(MX - 1, 0, -1):
    inv_f[i - 1] = inv_f[i] * i % MOD

# 从 n 个数中选 m 个数的方案数
def comb(n: int, m: int) -> int:
    return fac[n] * inv_f[m] * inv_f[n - m] % MOD if 0 <= m <= n else 0

class Solution:
    def solve(self, nums: List[int]) -> int:
        # 把预处理的逻辑写在 class 外面,这样只会初始化一次
java
class Solution {
    private static final int MOD = 1_000_000_007;
    private static final int MX = 100_001; // 根据题目数据范围修改

    private static final long[] F = new long[MX]; // F[i] = i!
    private static final long[] INV_F = new long[MX]; // INV_F[i] = i!^-1 = pow(i!, MOD-2)

    private static boolean initialized = false;

    // 这样写比 static block 快
    public Solution() {
        if (initialized) {
            return;
        }
        initialized = true;

        F[0] = 1;
        for (int i = 1; i < MX; i++) {
            F[i] = F[i - 1] * i % MOD;
        }

        INV_F[MX - 1] = pow(F[MX - 1], MOD - 2);
        for (int i = MX - 1; i > 0; i--) {
            INV_F[i - 1] = INV_F[i] * i % MOD;
        }
    }

    private long pow(long x, int n) {
        long res = 1;
        for (; n > 0; n /= 2) {
            if (n % 2 > 0) {
                res = res * x % MOD;
            }
            x = x * x % MOD;
        }
        return res;
    }

    // 从 n 个数中选 m 个数的方案数
    private long comb(int n, int m) {
        return m < 0 || m > n ? 0 : F[n] * INV_F[m] % MOD * INV_F[n - m] % MOD;
    }

    public int solve(int[] nums) {
        
    }
}
cpp
const int MOD = 1'000'000'007;
const int MX = 100'001; // 根据题目数据范围修改

long long F[MX]; // F[i] = i!
long long INV_F[MX]; // INV_F[i] = i!^-1 = qpow(i!, MOD-2)

long long qpow(long long x, int n) {
    long long res = 1;
    for (; n; n /= 2) {
        if (n % 2) {
            res = res * x % MOD;
        }
        x = x * x % MOD;
    }
    return res;
}

auto init = [] {
    F[0] = 1;
    for (int i = 1; i < MX; i++) {
        F[i] = F[i - 1] * i % MOD;
    }

    INV_F[MX - 1] = qpow(F[MX - 1], MOD - 2);
    for (int i = MX - 1; i; i--) {
        INV_F[i - 1] = INV_F[i] * i % MOD;
    }
    return 0;
}();

// 从 n 个数中选 m 个数的方案数
long long comb(int n, int m) {
    return m < 0 || m > n ? 0 : F[n] * INV_F[m] % MOD * INV_F[n - m] % MOD;
}

class Solution {
public:
    int solve(vector<int>& nums) {
        // 把预处理的逻辑写在 class 外面,这样只会初始化一次
    }
};
go
const mod = 1_000_000_007
const mx = 100_001 // 根据题目数据范围修改

var fac [mx]int  // fac[i] = i!
var invF [mx]int // invF[i] = i!^-1 = pow(i!, mod-2)

func init() {
    fac[0] = 1
    for i := 1; i < mx; i++ {
        fac[i] = fac[i-1] * i % mod
    }

    invF[mx-1] = pow(fac[mx-1], mod-2)
    for i := mx - 1; i > 0; i-- {
        invF[i-1] = invF[i] * i % mod
    }
}

func pow(x, n int) int {
    res := 1
    for ; n > 0; n /= 2 {
        if n%2 > 0 {
            res = res * x % mod
        }
        x = x * x % mod
    }
    return res
}

// 从 n 个数中选 m 个数的方案数
func comb(n, m int) int {
    if m < 0 || m > n {
        return 0
    }
    return fac[n] * invF[m] % mod * invF[n-m] % mod
}

func solve(nums []int) int {
    // 预处理的逻辑写在 init 函数中,这样只会初始化一次
}

关于生成函数的题目,见本题单的「§2.5 生成函数」。

关于计数 DP 的题目,见 动态规划题单 的「§7.7 计数 DP」。

§2.3 放球问题 ​

图解:多重集组合数

思维扩展:

§2.4 容斥原理 ​

部分题目有其他解法,难度分仅供参考。

§2.5 生成函数(母函数) ​

注:贡献法移至贪心与思维题单。

三、概率期望 ​

随机数据下显著更快的算法:

四、博弈论 ​

五、计算几何 ​

§5.1 点、线 ​

§5.2 圆 ​

§5.3 矩形、多边形 ​

§5.4 凸包 ​

模板:

py
class Vec:
    __slots__ = 'x', 'y'

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __sub__(self, b: "Vec") -> "Vec":
        return Vec(self.x - b.x, self.y - b.y)

    def det(self, b: "Vec") -> int:
        return self.x * b.y - self.y * b.x


# Andrew 算法,计算 points 的凸包(逆时针顺序)
# 时间复杂度 O(n log n),其中 n = len(points)
def convexHull(points: List[Vec]) -> List[Vec]:
    if len(points) <= 1:
        return points

    points.sort(key=lambda p: (p.x, p.y))

    q = []

    # 计算下凸包(从左到右)
    for p in points:
        # 新来的点 p,能否让旧的点变成在凸包内的点? ->  需要判断向量左右关系  ->  det
        while len(q) > 1 and (q[-1] - q[-2]).det(p - q[-1]) <= 0:
            q.pop()
        q.append(p)

    # 计算上凸包(从右到左)
    # 注意下凸包的最后一个点,已经是上凸包的(右边)第一个点了,所以从 n-2 开始遍历
    lower_size = len(q)
    for i in range(len(points) - 2, -1, -1):
        p = points[i]
        while len(q) > lower_size and (q[-1] - q[-2]).det(p - q[-1]) <= 0:
            q.pop()
        q.append(p)

    # 此时首尾是同一个点 points[0],需要去掉
    q.pop()

    return q
java
class Vec {
    int x, y;

    Vec(int x, int y) {
        this.x = x;
        this.y = y;
    }

    Vec sub(Vec b) {
        return new Vec(x - b.x, y - b.y);
    }

    long det(Vec b) {
        return (long) x * b.y - (long) y * b.x;
    }
}

class Solution {
    // Andrew 算法,计算 points 的凸包(逆时针顺序)
    // 时间复杂度 O(n log n),其中 n = points.size()
    public List<Vec> convexHull(List<Vec> points) {
        if (points.size() <= 1) {
            return points;
        }

        points.sort((a, b) -> a.x != b.x ? a.x - b.x : a.y - b.y);

        List<Vec> q = new ArrayList<>();

        // 计算下凸包(从左到右)
        for (Vec p : points) {
            // 新来的点 p,能否让旧的点变成在凸包内的点? ->  需要判断向量左右关系  ->  det
            while (q.size() > 1 && q.getLast().sub(q.get(q.size() - 2)).det(p.sub(q.getLast())) <= 0) {
                q.removeLast();
            }
            q.add(p);
        }

        // 计算上凸包(从右到左)
        // 注意下凸包的最后一个点,已经是上凸包的(右边)第一个点了,所以从 n-2 开始遍历
        int lowerSize = q.size();
        for (int i = points.size() - 2; i >= 0; i--) {
            Vec p = points.get(i);
            while (q.size() > lowerSize && q.getLast().sub(q.get(q.size() - 2)).det(p.sub(q.getLast())) <= 0) {
                q.removeLast();
            }
            q.add(p);
        }

        // 此时首尾是同一个点 points[0],需要去掉
        q.removeLast();

        return q;
    }
}
cpp
struct Vec {
    int x, y;

    Vec sub(const Vec& b) const {
        return {x - b.x, y - b.y};
    }

    long long det(const Vec& b) const {
        return 1LL * x * b.y - 1LL * y * b.x;
    }
};

// Andrew 算法,计算 points 的凸包(逆时针顺序)
// 时间复杂度 O(n log n),其中 n = points.size()
vector<Vec> convexHull(vector<Vec>& points) {
    ranges::sort(points, {}, [](auto& p) { return pair(p.x, p.y); });

    vector<Vec> q;

    // 计算下凸包(从左到右)
    for (auto& p : points) {
        // 新来的点 p,能否让旧的点变成在凸包内的点? ->  需要判断向量左右关系  ->  det
        while (q.size() > 1 && q.back().sub(q[q.size() - 2]).det(p.sub(q.back())) <= 0) {
            q.pop_back();
        }
        q.push_back(p);
    }

    // 计算上凸包(从右到左)
    // 注意下凸包的最后一个点,已经是上凸包的(右边)第一个点了,所以从 n-2 开始遍历
    int lower_size = q.size();
    for (int i = (int) points.size() - 2; i >= 0; i--) {
        auto& p = points[i];
        while (q.size() > lower_size && q.back().sub(q[q.size() - 2]).det(p.sub(q.back())) <= 0) {
            q.pop_back();
        }
        q.push_back(p);
    }

    // 此时首尾是同一个点 points[0],需要去掉
    q.pop_back();

    return q;
}
go
type vec struct{ x, y int }

func (a vec) sub(b vec) vec { return vec{a.x - b.x, a.y - b.y} }
func (a vec) det(b vec) int { return a.x*b.y - a.y*b.x }

// Andrew 算法,计算 points 的凸包(逆时针顺序)
// 时间复杂度 O(n log n),其中 n = len(points)
func convexHull(points []vec) []vec {
    if len(points) <= 1 {
        return points
    }

    slices.SortFunc(points, func(a, b vec) int { return cmp.Or(a.x-b.x, a.y-b.y) })

    q := []vec{}

    // 计算下凸包(从左到右)
    for _, p := range points {
        // 新来的点 p,能否让旧的点变成在凸包内的点? ->  需要判断向量左右关系  ->  det
        for len(q) > 1 && q[len(q)-1].sub(q[len(q)-2]).det(p.sub(q[len(q)-1])) <= 0 {
            q = q[:len(q)-1]
        }
        q = append(q, p)
    }

    // 计算上凸包(从右到左)
    // 注意下凸包的最后一个点,已经是上凸包的(右边)第一个点了,所以从 n-2 开始遍历
    lowerSize := len(q)
    for i := len(points) - 2; i >= 0; i-- {
        p := points[i]
        for len(q) > lowerSize && q[len(q)-1].sub(q[len(q)-2]).det(p.sub(q[len(q)-1])) <= 0 {
            q = q[:len(q)-1]
        }
        q = append(q, p)
    }

    // 此时首尾是同一个点 points[0],需要去掉
    q = q[:len(q)-1]

    return q
}

另见 动态规划题单 的「§11.7 斜率优化 DP」。

§5.5 其他 ​

六、随机算法 ​

§6.1 随机数 ​

§6.2 随机化技巧 ​

七、杂项 ​

§7.1 回文数 ​

从小到大枚举回文数的模板(从 1 开始枚举):

py
def gen_palindrome() -> Iterator[int]:
    base = 1
    while True:
        # 生成奇数长度回文数,例如 base = 10,生成的范围是 101 ~ 999
        for i in range(base, base * 10):
            s = str(i)
            x = int(s + s[::-1][1:])
            yield x

        # 生成偶数长度回文数,例如 base = 10,生成的范围是 1001 ~ 9999
        for i in range(base, base * 10):
            s = str(i)
            x = int(s + s[::-1])
            yield x

        base *= 10


for x in gen_palindrome():
    if x > 10 ** 9:  # 根据题目调整
        break
    # 处理 x 的逻辑写下面
    print(x)
java
class Solution {
    private static final long MX = 10_000_000_000L; // 根据题目调整
    private static final List<Long> palindromes = new ArrayList<>();
    private static boolean initialized = false;

    // 这样写比 static block 快
    public Solution() {
        if (initialized) {
            return;
        }
        initialized = true;

        for (int base = 1; ; base *= 10) {
            // 生成奇数长度回文数,例如 base = 10,生成的范围是 101 ~ 999
            for (int i = base; i < base * 10; i++) {
                long x = i;
                for (int t = i / 10; t > 0; t /= 10) {
                    x = x * 10 + t % 10;
                }
                if (x > MX) {
                    return;
                }
                palindromes.add(x);
            }

            // 生成偶数长度回文数,例如 base = 10,生成的范围是 1001 ~ 9999
            for (int i = base; i < base * 10; i++) {
                long x = i;
                for (int t = i; t > 0; t /= 10) {
                    x = x * 10 + t % 10;
                }
                if (x > MX) {
                    return;
                }
                palindromes.add(x);
            }
        }
    }

    public int solve(int n) {
        
    }
}
cpp
const long long MX = 10'000'000'000LL; // 根据题目调整
vector<long long> palindromes;

// 预处理 [1,MX] 中的回文数
auto init = []() {
    for (int base = 1; ; base *= 10) {
        // 生成奇数长度回文数,例如 base = 10,生成的范围是 101 ~ 999
        for (int i = base; i < base * 10; i++) {
            long long x = i;
            for (int t = i / 10; t > 0; t /= 10) {
                x = x * 10 + t % 10;
            }
            if (x > MX) {
                return 0;
            }
            palindromes.push_back(x);
        }

        // 生成偶数长度回文数,例如 base = 10,生成的范围是 1001 ~ 9999
        for (int i = base; i < base * 10; i++) {
            long long x = i;
            for (int t = i; t > 0; t /= 10) {
                x = x * 10 + t % 10;
            }
            if (x > MX) {
                return 0;
            }
            palindromes.push_back(x);
        }
    }
}();
go
const mx = 10_000_000_000 // 根据题目调整

var palindromes []int

// 预处理 [1,mx] 中的回文数
func init() {
    for base := 1; ; base *= 10 {
        // 生成奇数长度回文数,例如 base = 10,生成的范围是 101 ~ 999
        for i := base; i < base*10; i++ {
            x := i
            for t := i / 10; t > 0; t /= 10 {
                x = x*10 + t%10
            }
            if x > mx {
                return
            }
            palindromes = append(palindromes, x)
        }

        // 生成偶数长度回文数,例如 base = 10,生成的范围是 1001 ~ 9999
        for i := base; i < base*10; i++ {
            x := i
            for t := i; t > 0; t /= 10 {
                x = x*10 + t%10
            }
            if x > mx {
                return
            }
            palindromes = append(palindromes, x)
        }
    }
}

§7.2 整数拆分 ​

§7.3 曼哈顿距离与切比雪夫距离 ​

§7.4 多项式 ​

另见本题单的「§2.6 生成函数」。

§7.5 快速沃尔什变换(FWT) ​

§7.6 拉格朗日插值 ​

§7.7 线性基 ​

见 位运算题单。

§7.8 摩尔投票法 ​

§7.9 其他 ​

算法题单 ​

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

如果你发现有题目可以补充进来,欢迎评论反馈。