第14章:数学与位运算技巧

数学思维是算法问题的基石。许多看似复杂的算法题,一旦发现其中的数学规律,就能用简洁优雅的方式解决。对于有数学和函数式编程背景的工程师来说,这种思维方式尤其自然——我们习惯于寻找问题的代数结构,用数学归纳法证明正确性,用位运算优化性能。本章将系统梳理 LeetCode 中常见的数学技巧,帮助你建立从数学角度审视算法问题的直觉。

14.1 位运算:异或的妙用、位掩码技巧

位运算基础操作

位运算直接操作二进制位,是计算机最底层也是最高效的运算。掌握位运算不仅能优化代码性能,更重要的是它提供了一种全新的问题视角。

基本位运算符:

  • & (AND):两位都为1时结果为1
  • | (OR):至少一位为1时结果为1
  • ^ (XOR):两位不同时结果为1
  • ~ (NOT):按位取反
  • << (左移):相当于乘以2的幂
  • >> (右移):相当于除以2的幂

常用位运算技巧:

# 判断奇偶
is_odd = n & 1

# 获取第i位
bit_i = (n >> i) & 1

# 设置第i位为1
n |= (1 << i)

# 清除第i位
n &= ~(1 << i)

# 翻转第i位
n ^= (1 << i)

# 清除最低位的1
n &= (n - 1)

# 获取最低位的1
lowest_bit = n & (-n)

# 判断是否为2的幂
is_power_of_2 = n > 0 and (n & (n - 1)) == 0

异或运算的性质与应用

异或运算有几个重要的代数性质,这使得它在算法中有独特的应用:

  1. 自反性a ^ a = 0
  2. 恒等性a ^ 0 = a
  3. 交换律a ^ b = b ^ a
  4. 结合律(a ^ b) ^ c = a ^ (b ^ c)

这些性质的直接应用:找出数组中只出现一次的数字。

def singleNumber(nums):
    """#136: 数组中所有数字都出现两次,只有一个数字出现一次"""
    result = 0
    for num in nums:
        result ^= num
    return result
    # 原理:相同的数字异或为0,0异或任何数等于该数本身

位掩码与状态压缩

位掩码是用二进制位表示集合或状态的技术。每一位代表一个元素是否存在或某个状态是否开启。

class Bitmask:
    def __init__(self):
        self.mask = 0

    def add(self, i):
        """添加元素i到集合"""
        self.mask |= (1 << i)

    def remove(self, i):
        """从集合中移除元素i"""
        self.mask &= ~(1 << i)

    def contains(self, i):
        """检查集合是否包含元素i"""
        return (self.mask >> i) & 1

    def intersection(self, other):
        """求两个集合的交集"""
        return self.mask & other.mask

    def union(self, other):
        """求两个集合的并集"""
        return self.mask | other.mask

实例分析:#137 只出现一次的数字 II

题目:数组中每个元素都出现三次,只有一个元素出现一次,找出这个元素。

这题不能简单地用异或解决,需要更巧妙的位运算技巧:

def singleNumber(nums):
    """
    思路:统计每一位上1出现的次数,如果某位上1的个数不是3的倍数,
    说明只出现一次的数在该位上是1
    """
    result = 0
    for i in range(32):  # 处理32位整数的每一位
        bit_sum = 0
        for num in nums:
            # 统计第i位上1的个数
            bit_sum += (num >> i) & 1

        # 如果该位上1的个数不是3的倍数,结果的该位为1
        if bit_sum % 3:
            # Python需要特殊处理负数
            if i == 31:  # 符号位
                result -= (1 << 31)
            else:
                result |= (1 << i)

    return result

更优雅的解法使用有限状态机:

def singleNumber_FSM(nums):
    """
    使用两个变量 ones 和 twos 记录各位出现1的次数模3的结果
    ones: 记录出现1次的位
    twos: 记录出现2次的位
    """
    ones = twos = 0
    for num in nums:
        # 更新twos:原来出现1次的位,现在又出现了,变成出现2次
        twos |= ones & num
        # 更新ones:异或操作,新出现的位变成1,已有的位变成0
        ones ^= num
        # 清除出现3次的位(ones和twos中都有的位)
        threes = ones & twos
        ones &= ~threes
        twos &= ~threes

    return ones

实例分析:#260 只出现一次的数字 III

题目:数组中有两个元素只出现一次,其余元素都出现两次,找出这两个元素。

def singleNumber(nums):
    """
    思路:

    1. 先对所有数字异或,得到两个单独数字的异或结果
    2. 找到异或结果中任意一个为1的位(说明两个数在该位不同)
    3. 根据该位将数组分成两组,每组包含一个单独的数字
    """
    # 第一步:得到两个单独数字的异或结果
    xor_result = 0
    for num in nums:
        xor_result ^= num

    # 第二步:找到最低位的1(两个数字不同的位)
    diff_bit = xor_result & (-xor_result)

    # 第三步:根据该位分组并分别异或
    num1 = num2 = 0
    for num in nums:
        if num & diff_bit:
            num1 ^= num
        else:
            num2 ^= num

    return [num1, num2]

14.2 数论基础:GCD、质数、模运算

最大公约数与最小公倍数

欧几里得算法(辗转相除法)是计算最大公约数的经典算法:

def gcd(a, b):
    """递归版本"""
    if b == 0:
        return a
    return gcd(b, a % b)

def gcd_iterative(a, b):
    """迭代版本"""
    while b:
        a, b = b, a % b
    return a

def lcm(a, b):
    """最小公倍数 = a * b / gcd(a, b)"""
    return a * b // gcd(a, b)

扩展欧几里得算法可以找到满足 ax + by = gcd(a, b) 的整数解:

def extended_gcd(a, b):
    """
    返回 (gcd, x, y) 使得 ax + by = gcd(a, b)
    """
    if b == 0:
        return a, 1, 0

    gcd, x1, y1 = extended_gcd(b, a % b)
    x = y1
    y = x1 - (a // b) * y1

    return gcd, x, y

质数判定与筛法

判断一个数是否为质数:

def is_prime(n):
    """O(√n) 时间复杂度"""
    if n < 2:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False

    # 只需检查到 √n
    i = 3
    while i * i <= n:
        if n % i == 0:
            return False
        i += 2
    return True

埃拉托斯特尼筛法(Sieve of Eratosthenes)找出范围内所有质数:

def sieve_of_eratosthenes(n):
    """找出所有小于等于n的质数"""
    if n < 2:
        return []

    is_prime = [True] * (n + 1)
    is_prime[0] = is_prime[1] = False

    for i in range(2, int(n**0.5) + 1):
        if is_prime[i]:
            # 标记i的所有倍数为非质数
            for j in range(i*i, n + 1, i):
                is_prime[j] = False

    return [i for i in range(n + 1) if is_prime[i]]

模运算的性质

模运算在算法中经常用于防止整数溢出和循环处理:

# 模运算基本性质
# (a + b) % m = ((a % m) + (b % m)) % m
# (a * b) % m = ((a % m) * (b % m)) % m
# (a - b) % m = ((a % m) - (b % m) + m) % m  # 注意处理负数

def mod_inverse(a, m):
    """
    计算a在模m下的乘法逆元(要求gcd(a, m) = 1)
    使用扩展欧几里得算法
    """
    gcd, x, _ = extended_gcd(a, m)
    if gcd != 1:
        return None  # 逆元不存在
    return x % m

快速幂算法

计算 $a^n \bmod m$ 的高效算法:

def power_mod(a, n, m):
    """
    计算 (a^n) % m,时间复杂度 O(log n)
    基于二进制表示:a^n = a^(b_k * 2^k + ... + b_1 * 2 + b_0)
    """
    result = 1
    a = a % m

    while n > 0:
        if n & 1:  # 如果n的最低位是1
            result = (result * a) % m
        a = (a * a) % m  # a = a^2
        n >>= 1  # n = n // 2

    return result

实例分析:#166 分数到小数

题目:给定两个整数,表示分数的分子和分母,返回小数形式的字符串。如果是循环小数,将循环部分括在括号内。

def fractionToDecimal(numerator, denominator):
    """
    关键:检测循环的方法是记录余数出现的位置
    当余数重复出现时,说明开始循环
    """
    if numerator == 0:
        return "0"

    result = []

    # 处理符号
    if (numerator < 0) != (denominator < 0):
        result.append("-")

    numerator, denominator = abs(numerator), abs(denominator)

    # 整数部分
    result.append(str(numerator // denominator))
    numerator %= denominator

    if numerator == 0:
        return "".join(result)

    result.append(".")

    # 小数部分:记录余数出现的位置
    remainder_map = {}

    while numerator != 0:
        if numerator in remainder_map:
            # 发现循环
            index = remainder_map[numerator]
            result.insert(index, "(")
            result.append(")")
            break

        remainder_map[numerator] = len(result)
        numerator *= 10
        result.append(str(numerator // denominator))
        numerator %= denominator

    return "".join(result)

实例分析:#50 Pow(x, n)

题目:实现 pow(x, n),即计算 x 的 n 次幂。

def myPow(x, n):
    """
    使用快速幂算法,时间复杂度 O(log n)
    """
    if n == 0:
        return 1

    if n < 0:
        x = 1 / x
        n = -n

    result = 1
    current_product = x

    while n > 0:
        if n & 1:  # 如果n是奇数
            result *= current_product
        current_product *= current_product
        n >>= 1

    return result

def myPow_recursive(x, n):
    """递归版本,更直观"""
    if n == 0:
        return 1

    if n < 0:
        return 1 / myPow_recursive(x, -n)

    if n & 1:  # 奇数
        return x * myPow_recursive(x * x, n >> 1)
    else:  # 偶数
        return myPow_recursive(x * x, n >> 1)

14.3 组合数学:排列组合公式的应用

排列组合基础公式

组合数学为我们提供了计数的系统方法。掌握这些公式不仅能直接解决计数问题,还能帮助我们分析算法的时间复杂度。

基本公式:

  • 排列数:$P(n, k) = \frac{n!}{(n-k)!}$
  • 组合数:$C(n, k) = \binom{n}{k} = \frac{n!}{k!(n-k)!}$

组合数的性质:

  • 对称性:$\binom{n}{k} = \binom{n}{n-k}$
  • 帕斯卡恒等式:$\binom{n}{k} = \binom{n-1}{k-1} + \binom{n-1}{k}$
  • 范德蒙德恒等式:$\sum_{k=0}^r \binom{m}{k}\binom{n}{r-k} = \binom{m+n}{r}$

计算组合数的实现:

def factorial(n):
    """计算阶乘"""
    if n <= 1:
        return 1
    result = 1
    for i in range(2, n + 1):
        result *= i
    return result

def combination(n, k):
    """计算组合数 C(n, k)"""
    if k > n or k < 0:
        return 0
    if k == 0 or k == n:
        return 1

    # 利用对称性优化
    k = min(k, n - k)

    # 避免溢出的计算方法
    result = 1
    for i in range(k):
        result = result * (n - i) // (i + 1)

    return result

def combination_dp(n, k):
    """动态规划计算组合数,基于帕斯卡三角形"""
    dp = [[0] * (k + 1) for _ in range(n + 1)]

    for i in range(n + 1):
        dp[i][0] = 1
        for j in range(1, min(i, k) + 1):
            dp[i][j] = dp[i-1][j-1] + dp[i-1][j]

    return dp[n][k]

下一个排列算法

下一个排列是组合数学中的经典问题,要求找到当前排列在字典序中的下一个排列。

算法思路:

  1. 从右往左找第一个升序对 (i, i+1),即 nums[i] < nums[i+1]
  2. 从右往左找第一个大于 nums[i] 的元素 nums[j]
  3. 交换 nums[i] 和 nums[j]
  4. 反转 i+1 到末尾的所有元素
def nextPermutation(nums):
    """
    #31: 下一个排列
    修改原数组,将其变为下一个排列
    """
    n = len(nums)

    # 步骤1:找到第一个升序对
    i = n - 2
    while i >= 0 and nums[i] >= nums[i + 1]:
        i -= 1

    if i >= 0:  # 如果不是最后一个排列
        # 步骤2:找到第一个大于nums[i]的元素
        j = n - 1
        while j > i and nums[j] <= nums[i]:
            j -= 1

        # 步骤3:交换
        nums[i], nums[j] = nums[j], nums[i]

    # 步骤4:反转i+1到末尾
    left, right = i + 1, n - 1
    while left < right:
        nums[left], nums[right] = nums[right], nums[left]
        left += 1
        right -= 1

康托展开与逆康托展开

康托展开将排列映射到自然数,可以在排列和序号之间建立一一对应关系。

def cantor_encode(perm):
    """
    康托展开:将排列转换为序号
    序号 = Σ(smaller[i] * (n-i-1)!)
    其中 smaller[i] 是 perm[i] 右边比它小的元素个数
    """
    n = len(perm)
    result = 0
    factorial = [1] * n

    # 预计算阶乘
    for i in range(1, n):
        factorial[i] = factorial[i-1] * i

    for i in range(n):
        smaller = 0
        for j in range(i + 1, n):
            if perm[j] < perm[i]:
                smaller += 1
        result += smaller * factorial[n - i - 1]

    return result

def cantor_decode(n, k):
    """
    逆康托展开:将序号转换为排列
    """
    nums = list(range(1, n + 1))
    factorial = [1] * n

    for i in range(1, n):
        factorial[i] = factorial[i-1] * i

    result = []
    k -= 1  # 转换为0索引

    for i in range(n, 0, -1):
        index = k // factorial[i-1]
        result.append(nums[index])
        nums.pop(index)
        k %= factorial[i-1]

    return result

卡特兰数的应用

卡特兰数是组合数学中的重要数列,在许多计数问题中出现:

$C_n = \frac{1}{n+1}\binom{2n}{n} = \frac{(2n)!}{(n+1)!n!}$

递推关系:$C_{n+1} = \sum_{i=0}^n C_i C_{n-i}$

卡特兰数的应用场景:

  • n对括号的合法组合数
  • n+1个节点的不同二叉搜索树数量
  • n×n网格从左下到右上不越过对角线的路径数
  • n个元素的出栈序列数
def catalan_number(n):
    """计算第n个卡特兰数"""
    if n <= 1:
        return 1

    # 方法1:使用公式
    result = 1
    for i in range(n):
        result = result * (2 * n - i) // (i + 1)
    return result // (n + 1)

def catalan_dp(n):
    """动态规划计算卡特兰数"""
    dp = [0] * (n + 1)
    dp[0] = dp[1] = 1

    for i in range(2, n + 1):
        for j in range(i):
            dp[i] += dp[j] * dp[i - 1 - j]

    return dp[n]

实例分析:#60 排列序列

题目:给出集合 [1,2,3,...,n],返回所有排列按字典序排序后的第 k 个排列。

def getPermutation(n, k):
    """
    使用康托展开的思想:
    第k个排列可以通过确定每一位的数字来构造
    """
    nums = list(range(1, n + 1))
    factorial = [1] * n

    # 计算阶乘
    for i in range(1, n):
        factorial[i] = factorial[i-1] * i

    k -= 1  # 转换为0索引
    result = []

    for i in range(n, 0, -1):
        # 当前位置应该选择第index个数字
        index = k // factorial[i-1]
        result.append(str(nums[index]))
        nums.pop(index)
        k %= factorial[i-1]

    return ''.join(result)

14.4 几何问题:凸包、最近点对

点、线、面的基本计算

几何问题的基础是向量运算和解析几何:

import math

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance_to(self, other):
        """两点间距离"""
        return ((self.x - other.x)**2 + (self.y - other.y)**2)**0.5

    def __sub__(self, other):
        """向量减法"""
        return Point(self.x - other.x, self.y - other.y)

def dot_product(p1, p2):
    """点积:a·b = |a||b|cos(θ)"""
    return p1.x * p2.x + p1.y * p2.y

def cross_product(p1, p2):
    """叉积:a×b = |a||b|sin(θ)"""
    return p1.x * p2.y - p1.y * p2.x

def orientation(p, q, r):
    """
    判断三点的方向关系:
    返回值 > 0: 逆时针
    返回值 = 0: 共线
    返回值 < 0: 顺时针
    """
    return cross_product(q - p, r - q)

def point_on_segment(p, q, r):
    """判断点q是否在线段pr上"""
    return (min(p.x, r.x) <= q.x <= max(p.x, r.x) and
            min(p.y, r.y) <= q.y <= max(p.y, r.y) and
            orientation(p, q, r) == 0)

def segments_intersect(p1, q1, p2, q2):
    """判断两线段是否相交"""
    o1 = orientation(p1, q1, p2)
    o2 = orientation(p1, q1, q2)
    o3 = orientation(p2, q2, p1)
    o4 = orientation(p2, q2, q1)

    # 一般情况:两线段跨立
    if o1 * o2 < 0 and o3 * o4 < 0:
        return True

    # 特殊情况:共线且重叠
    if o1 == 0 and point_on_segment(p1, p2, q1):
        return True
    if o2 == 0 and point_on_segment(p1, q2, q1):
        return True
    if o3 == 0 and point_on_segment(p2, p1, q2):
        return True
    if o4 == 0 and point_on_segment(p2, q1, q2):
        return True

    return False

叉积与几何判定

叉积是二维几何中最重要的工具,可以用于:

  • 判断点的位置关系
  • 计算多边形面积
  • 判断凸性
def polygon_area(points):
    """
    计算多边形面积(可以是凸多边形或凹多边形)
    使用鞋带公式(Shoelace formula)
    """
    n = len(points)
    area = 0

    for i in range(n):
        j = (i + 1) % n
        area += points[i][0] * points[j][1]
        area -= points[j][0] * points[i][1]

    return abs(area) / 2

def is_convex_polygon(points):
    """判断多边形是否为凸多边形"""
    n = len(points)
    if n < 3:
        return False

    sign = 0
    for i in range(n):
        p1 = points[i]
        p2 = points[(i + 1) % n]
        p3 = points[(i + 2) % n]

        cross = cross_product(
            Point(p2[0] - p1[0], p2[1] - p1[1]),
            Point(p3[0] - p2[0], p3[1] - p2[1])
        )

        if cross != 0:
            if sign == 0:
                sign = 1 if cross > 0 else -1
            elif sign * cross < 0:
                return False

    return True

凸包算法

凸包是包含所有给定点的最小凸多边形。Graham扫描是经典的凸包算法:

def convex_hull_graham(points):
    """
    Graham扫描算法求凸包
    时间复杂度:O(n log n)
    """
    n = len(points)
    if n < 3:
        return points

    # 找到最下方的点(y坐标最小,相同则x坐标最小)
    start = min(points, key=lambda p: (p[1], p[0]))

    # 按极角排序
    def polar_angle(p):
        dx = p[0] - start[0]
        dy = p[1] - start[1]
        return (math.atan2(dy, dx), dx*dx + dy*dy)

    sorted_points = sorted(points, key=polar_angle)

    # 构建凸包
    hull = []
    for p in sorted_points:
        # 移除非左转的点
        while len(hull) >= 2:
            o = orientation(
                Point(*hull[-2]), 
                Point(*hull[-1]), 
                Point(*p)
            )
            if o <= 0:  # 不是逆时针
                hull.pop()
            else:
                break
        hull.append(p)

    return hull

def convex_hull_jarvis(points):
    """
    Jarvis步进法(礼品包装算法)
    时间复杂度:O(nh),h是凸包上的点数
    """
    n = len(points)
    if n < 3:
        return points

    # 找到最左边的点
    l = min(range(n), key=lambda i: points[i][0])

    hull = []
    p = l

    while True:
        hull.append(points[p])

        # 找到最逆时针的点
        q = (p + 1) % n
        for i in range(n):
            o = orientation(
                Point(*points[p]),
                Point(*points[i]),
                Point(*points[q])
            )
            if o > 0:  # i在pq的左边
                q = i

        p = q
        if p == l:  # 回到起点
            break

    return hull

扫描线算法

扫描线是处理几何问题的重要技术,特别适合处理区间相关问题:

def rectangle_area_union(rectangles):
    """
    计算多个矩形的并集面积
    使用扫描线算法
    """
    events = []

    # 创建事件:矩形的左右边界
    for x1, y1, x2, y2 in rectangles:
        events.append((x1, y1, y2, 1))   # 左边界,开始
        events.append((x2, y1, y2, -1))  # 右边界,结束

    events.sort()

    def calculate_y_coverage(intervals):
        """计算y轴上的覆盖长度"""
        if not intervals:
            return 0

        intervals.sort()
        total = 0
        start, end = intervals[0]

        for s, e in intervals[1:]:
            if s <= end:
                end = max(end, e)
            else:
                total += end - start
                start, end = s, e

        total += end - start
        return total

    total_area = 0
    prev_x = 0
    active_intervals = []

    for x, y1, y2, typ in events:
        # 计算之前的面积
        if active_intervals and x > prev_x:
            y_coverage = calculate_y_coverage(active_intervals)
            total_area += y_coverage * (x - prev_x)

        # 更新活动区间
        if typ == 1:
            active_intervals.append((y1, y2))
        else:
            active_intervals.remove((y1, y2))

        prev_x = x

    return total_area

实例分析:#149 直线上最多的点数

题目:给定平面上的n个点,求最多有多少个点在同一条直线上。

def maxPoints(points):
    """
    关键:用斜率来表示直线,但要处理精度问题
    技巧:用最简分数表示斜率,避免浮点数误差
    """
    if len(points) <= 2:
        return len(points)

    def gcd(a, b):
        while b:
            a, b = b, a % b
        return a

    max_count = 0

    for i in range(len(points)):
        slopes = {}
        duplicate = 0
        local_max = 0

        for j in range(i + 1, len(points)):
            dx = points[j][0] - points[i][0]
            dy = points[j][1] - points[i][1]

            if dx == 0 and dy == 0:
                # 重复点
                duplicate += 1
                continue

            # 计算最简分数形式的斜率
            g = gcd(dx, dy)
            dx, dy = dx // g, dy // g

            # 统一符号:保证dx为正或(dx=0时dy为正)
            if dx < 0:
                dx, dy = -dx, -dy
            elif dx == 0:
                dy = abs(dy)

            slope = (dx, dy)
            slopes[slope] = slopes.get(slope, 0) + 1
            local_max = max(local_max, slopes[slope])

        max_count = max(max_count, local_max + duplicate + 1)

    return max_count

实例分析:#223 矩形面积

题目:给定两个矩形的坐标,计算它们覆盖的总面积。

def computeArea(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2):
    """
    计算两个矩形覆盖的总面积
    总面积 = 矩形1面积 + 矩形2面积 - 重叠面积
    """
    # 两个矩形的面积
    area1 = (ax2 - ax1) * (ay2 - ay1)
    area2 = (bx2 - bx1) * (by2 - by1)

    # 计算重叠部分
    # 重叠矩形的左下角
    overlap_x1 = max(ax1, bx1)
    overlap_y1 = max(ay1, by1)
    # 重叠矩形的右上角
    overlap_x2 = min(ax2, bx2)
    overlap_y2 = min(ay2, by2)

    # 计算重叠面积
    overlap_area = 0
    if overlap_x2 > overlap_x1 and overlap_y2 > overlap_y1:
        overlap_area = (overlap_x2 - overlap_x1) * (overlap_y2 - overlap_y1)

    return area1 + area2 - overlap_area

本章小结

数学与位运算技巧是算法优化的利器。通过本章的学习,我们掌握了:

位运算核心技巧

  • 异或运算:利用自反性解决配对问题
  • 位掩码:用二进制位表示集合状态,实现高效的集合操作
  • 位操作技巧:清除最低位、判断2的幂等常用操作

数论基础工具

  • 欧几里得算法:高效计算最大公约数
  • 模运算性质:防止溢出,处理循环
  • 快速幂:对数时间计算幂运算

组合数学方法

  • 排列组合公式:解决计数问题的基础
  • 康托展开:排列与序号的双向映射
  • 卡特兰数:识别经典的递归计数模式

几何算法精华

  • 叉积应用:判断方向、计算面积
  • 凸包算法:Graham扫描的单调栈思想
  • 扫描线技术:处理区间和矩形问题

关键公式汇总:

  • 异或性质:$a \oplus a = 0$,$a \oplus 0 = a$
  • 组合数递推:$\binom{n}{k} = \binom{n-1}{k-1} + \binom{n-1}{k}$
  • 卡特兰数:$C_n = \frac{1}{n+1}\binom{2n}{n}$
  • 叉积几何意义:$\vec{a} \times \vec{b} = |a||b|\sin\theta$

常见陷阱与错误

位运算优先级陷阱

# 错误:位运算优先级低于比较运算符
if n & 1 == 0:  # 实际是 n & (1 == 0)
    pass

# 正确:加括号明确优先级
if (n & 1) == 0:
    pass

整数溢出处理

# Python 自动处理大整数,但其他语言需要注意
# C++/Java 中计算组合数容易溢出

# 避免溢出的技巧:边乘边除
def combination_safe(n, k):
    result = 1
    for i in range(k):
        result = result * (n - i) // (i + 1)  # 先乘后除
    return result

浮点数精度问题

# 错误:直接比较浮点数
if slope1 == slope2:  # 可能因精度问题失败
    pass

# 正确:使用整数表示(如最简分数)
slope1 = (dx1 // gcd1, dy1 // gcd1)
slope2 = (dx2 // gcd2, dy2 // gcd2)
if slope1 == slope2:
    pass

# 或使用误差容忍
EPSILON = 1e-9
if abs(slope1 - slope2) < EPSILON:
    pass

边界条件处理

# 负数的位运算
# Python: -1 >> 1 = -1 (算术右移)
# 需要特别处理符号位

# 模运算的负数处理
def mod_safe(a, m):
    return ((a % m) + m) % m  # 确保结果非负

# 几何算法的特殊点
# 重复点、共线点、退化情况都需要特别处理

算法选择误区

  • 过度优化:不是所有问题都需要位运算,可读性也很重要
  • 精度 vs 效率:几何问题中,整数运算比浮点数更可靠
  • 空间换时间:预计算(如阶乘表)可以显著提升性能

记住:数学是算法的灵魂。当你遇到看似复杂的问题时,不妨先寻找其中的数学规律,往往能找到简洁优雅的解法。