前缀和与差分-入门到入魂

前缀和与差分:从入门到精通(Python版)

 

一、核心概念:用生活例子理解

 

1.1 前缀和:预支的智慧

 

想象你有一本存折,记录着每天的存款:

 

第1天:+100元

第2天:+50元

第3天:+200元

第4天:-30元

 

前缀和就是提前计算好"到第N天为止的总金额":

 

到第1天:100元

到第2天:150元

到第3天:350元

到第4天:320元

 

这样如果有人问"第2天到第4天的总收支",你只需用第4天的总和减去第1天的总和:320 - 100 = 220元。

 

1.2 差分:记账的艺术

 

现在换一种情况,银行经理说:

 

"给第2天到第4天每天加10元利息"

"给第1天到第3天每天减5元手续费"

 

差分就是聪明的记账方法:只在变化开始和结束的地方做标记,最后再统一计算。

 

二、一维前缀和深度解析

 

2.1 基础实现与原理

 

def build_prefix_sum(arr):

    """

    构建前缀和数组

    :param arr: 原始数组 [a0, a1, a2, ..., an-1]

    :return: 前缀和数组 [0, a0, a0+a1, a0+a1+a2, ...]

    """

    n = len(arr)

    prefix = [0] * (n + 1)

    for i in range(1, n + 1):

        prefix[i] = prefix[i-1] + arr[i-1]

    return prefix

 

def query_range_sum(prefix, l, r):

    """

    查询区间和

    :param prefix: 前缀和数组

    :param l: 区间左端点(从0开始)

    :param r: 区间右端点

    :return: arr[l] + arr[l+1] + ... + arr[r]

    """

    return prefix[r+1] - prefix[l]

 

# 示例

arr = [1, 3, 5, 7, 2, 4]

prefix = build_prefix_sum(arr)

print(f"原始数组: {arr}")

print(f"前缀和数组: {prefix}")

print(f"区间[1, 3]的和: {query_range_sum(prefix, 1, 3)}") # 输出15

 

2.2 高级应用:统计区间特征

 

def count_range_avg_above_threshold(arr, threshold):

    """

    统计有多少个区间的平均值大于threshold

    技巧:将每个元素减去threshold,问题转化为求区间和大于0的区间个数

    """

    n = len(arr)

    # 构建转换后的前缀和

    transformed = [x - threshold for x in arr]

    prefix = [0] * (n + 1)

    for i in range(1, n + 1):

        prefix[i] = prefix[i-1] + transformed[i-1]

    

    # 使用单调栈优化查找(这里简化为O(n²)实现)

    count = 0

    for i in range(n):

        for j in range(i, n):

            if prefix[j+1] - prefix[i] > 0:

                count += 1

    return count

 

# 示例

arr = [2, 1, 3]

print(f"平均值大于1的区间个数: {count_range_avg_above_threshold(arr, 1)}")

 

三、一维差分深度解析

 

3.1 基础实现与原理

 

def build_diff_array(arr):

    """

    构建差分数组

    :param arr: 原始数组

    :return: 差分数组 diff,其中diff[0]=arr[0], diff[i]=arr[i]-arr[i-1]

    """

    n = len(arr)

    diff = [0] * n

    diff[0] = arr[0]

    for i in range(1, n):

        diff[i] = arr[i] - arr[i-1]

    return diff

 

def range_add(diff, l, r, value):

    """

    区间增加操作

    :param diff: 差分数组

    :param l: 区间左端点

    :param r: 区间右端点

    :param value: 增加的值

    """

    diff[l] += value

    if r + 1 < len(diff):

        diff[r+1] -= value

 

def reconstruct_from_diff(diff):

    """

    从差分数组重建原数组

    """

    arr = [0] * len(diff)

    arr[0] = diff[0]

    for i in range(1, len(diff)):

        arr[i] = arr[i-1] + diff[i]

    return arr

 

# 示例

arr = [1, 3, 5, 7, 2, 4]

diff = build_diff_array(arr)

print(f"原始数组: {arr}")

print(f"差分数组: {diff}")

 

# 对区间[1, 3]每个元素加5

range_add(diff, 1, 3, 5)

new_arr = reconstruct_from_diff(diff)

print(f"修改后的数组: {new_arr}")

 

3.2 高级应用:多次区间操作优化

 

def multiple_range_operations(arr, operations):

    """

    处理多次区间操作

    :param arr: 原始数组

    :param operations: 操作列表,每个操作是(l, r, value)

    :return: 操作后的数组

    """

    n = len(arr)

    # 初始化差分数组(全0)

    diff = [0] * (n + 1) # 多一位用于处理边界

    

    # 先构建原始数组的差分表示

    diff[0] = arr[0]

    for i in range(1, n):

        diff[i] = arr[i] - arr[i-1]

    

    # 应用所有操作

    for l, r, value in operations:

        diff[l] += value

        if r + 1 < len(diff):

            diff[r+1] -= value

    

    # 重建数组

    result = [0] * n

    result[0] = diff[0]

    for i in range(1, n):

        result[i] = result[i-1] + diff[i]

    

    return result

 

# 示例

arr = [1, 3, 5, 7, 2, 4]

operations = [(1, 3, 5), (0, 2, -2), (4, 5, 3)]

result = multiple_range_operations(arr, operations)

print(f"多次操作后的结果: {result}")

 

四、二维前缀和与差分

 

4.1 二维前缀和

 

def build_2d_prefix(matrix):

    """

    构建二维前缀和矩阵

    """

    if not matrix or not matrix[0]:

        return []

    

    m, n = len(matrix), len(matrix[0])

    # 前缀和矩阵,大小(m+1) x (n+1)

    prefix = [[0] * (n+1) for _ in range(m+1)]

    

    for i in range(1, m+1):

        for j in range(1, n+1):

            prefix[i][j] = (prefix[i-1][j] + prefix[i][j-1] - 

                           prefix[i-1][j-1] + matrix[i-1][j-1])

    return prefix

 

def query_2d_range(prefix, x1, y1, x2, y2):

    """

    查询二维区间和

    """

    return (prefix[x2+1][y2+1] - prefix[x1][y2+1] - 

            prefix[x2+1][y1] + prefix[x1][y1])

 

# 示例

matrix = [

    [1, 2, 3],

    [4, 5, 6],

    [7, 8, 9]

]

prefix = build_2d_prefix(matrix)

print(f"矩阵左上角到右下角(1,1)到(2,2)的和: {query_2d_range(prefix, 1, 1, 2, 2)}")

 

4.2 二维差分

 

def build_2d_diff(matrix):

    """

    构建二维差分矩阵

    """

    m, n = len(matrix), len(matrix[0])

    diff = [[0] * (n+2) for _ in range(m+2)] # 周围多一圈0处理边界

    

    # 初始化差分矩阵

    for i in range(m):

        for j in range(n):

            diff[i+1][j+1] = (matrix[i][j] - 

                             (matrix[i-1][j] if i > 0 else 0) -

                             (matrix[i][j-1] if j > 0 else 0) +

                             (matrix[i-1][j-1] if i > 0 and j > 0 else 0))

    return diff

 

def range_add_2d(diff, x1, y1, x2, y2, value):

    """

    二维区间增加操作

    """

    diff[x1][y1] += value

    diff[x1][y2+1] -= value

    diff[x2+1][y1] -= value

    diff[x2+1][y2+1] += value

 

def reconstruct_from_2d_diff(diff, m, n):

    """

    从二维差分重建原矩阵

    """

    matrix = [[0] * n for _ in range(m)]

    

    # 计算前缀和

    for i in range(1, m+1):

        for j in range(1, n+1):

            diff[i][j] += diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1]

            matrix[i-1][j-1] = diff[i][j]

    

    return matrix

 

# 示例

matrix = [

    [1, 1, 1],

    [1, 1, 1],

    [1, 1, 1]

]

diff = build_2d_diff(matrix)

# 在2x2区域(从(0,0)到(1,1))每个元素加2

range_add_2d(diff, 0, 0, 1, 1, 2)

result = reconstruct_from_2d_diff(diff, 3, 3)

print("二维区间增加后的结果:")

for row in result:

    print(row)

 

五、实战应用:LeetCode经典题目

 

5.1 区域和检索 - 数组不可变 (LeetCode 303)

 

class NumArray:

    def __init__(self, nums: List[int]):

        self.prefix = [0] * (len(nums) + 1)

        for i in range(1, len(nums) + 1):

            self.prefix[i] = self.prefix[i-1] + nums[i-1]

 

    def sumRange(self, left: int, right: int) -> int:

        return self.prefix[right+1] - self.prefix[left]

 

5.2 二维区域和检索 - 矩阵不可变 (LeetCode 304)

 

class NumMatrix:

    def __init__(self, matrix: List[List[int]]):

        if not matrix or not matrix[0]:

            m, n = 0, 0

        else:

            m, n = len(matrix), len(matrix[0])

        self.prefix = [[0] * (n+1) for _ in range(m+1)]

        

        for i in range(1, m+1):

            for j in range(1, n+1):

                self.prefix[i][j] = (self.prefix[i-1][j] + self.prefix[i][j-1] - 

                                   self.prefix[i-1][j-1] + matrix[i-1][j-1])

 

    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:

        return (self.prefix[row2+1][col2+1] - self.prefix[row1][col2+1] - 

                self.prefix[row2+1][col1] + self.prefix[row1][col1])

 

5.3 航班预订统计 (LeetCode 1109)

 

def corpFlightBookings(bookings: List[List[int]], n: int) -> List[int]:

    diff = [0] * (n + 2) # 多两位处理边界

    

    for first, last, seats in bookings:

        diff[first] += seats

        if last + 1 <= n:

            diff[last + 1] -= seats

    

    # 重建答案

    result = [0] * n

    result[0] = diff[1]

    for i in range(1, n):

        result[i] = result[i-1] + diff[i+1]

    

    return result

 

六、性能分析与高级优化

 

6.1 时间复杂度对比

 

操作类型 暴力法 前缀和/差分法

单次区间查询 O(n) O(1)

单次区间修改 O(n) O(1)

m次查询 O(mn) O(n + m)

m次修改 O(mn) O(n + m)

 

6.2 空间复杂度优化

 

对于某些问题,我们不需要存储完整的前缀和/差分数组:

 

def max_subarray_sum(arr):

    """

    最大子数组和 - 空间优化版

    只需要前一个前缀和值,不需要整个数组

    """

    if not arr:

        return 0

    

    max_sum = arr[0]

    min_prefix = min(0, arr[0])

    current_prefix = arr[0]

    

    for i in range(1, len(arr)):

        current_prefix += arr[i]

        max_sum = max(max_sum, current_prefix - min_prefix)

        min_prefix = min(min_prefix, current_prefix)

    

    return max_sum

 

七、总结与进阶学习路径

 

7.1 核心思想总结

 

1. 前缀和:空间换时间,预处理O(n),查询O(1)

2. 差分:延迟更新,批量操作O(1),最终重建O(n)

3. 维度扩展:从一维到二维,原理相同但计算更复杂

4. 应用场景:区间查询、区间修改、统计问题等

 

7.2 进阶学习建议

 

1. 树状数组 (Fenwick Tree):支持动态更新的前缀和

2. 线段树 (Segment Tree):支持多种区间操作

3. 稀疏表 (Sparse Table):静态区间最值查询

4. 莫队算法:离线处理区间查询问题

 

7.3 练习题目推荐

 

1. ⭐⭐ 简单:LeetCode 303, 304, 1109

2. ⭐⭐⭐ 中等:LeetCode 560, 1248, 1314

3. ⭐⭐⭐⭐ 困难:LeetCode 1074, 327

 

通过系统学习前缀和与差分,你不仅能解决许多区间操作问题,还能为学习更高级的数据结构打下坚实基础。记住核心思想:预处理优化查询,差分优化更新,这是算法优化中的常用技巧。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值