备战蓝桥杯国赛【Day 4】

📌 前置知识速查

如果你还不熟悉差分数组,记住这两个公式:

一维 :区间 [l,r]xdiff[l]+=x, diff[r+1]-=x
二维 :子矩阵 (x1,y1)(x2,y2)x → 四角容斥(左上+, 右上-, 左下-, 右下+)


例题 1:区间更新(蓝桥云课 P3291)

项目 内容
链接 https://www.lanqiao.cn/problems/3291/learning/
时间限制 1s
空间限制 256MB
数据范围 n,m < 10^5,多组数据

题目描述

给定长度为 n 的数组 a[1..n],执行 m 次操作。每次操作给定 x,y,z,将下标 xy(包含)的所有元素加上 z。最后输出整个数组。

输入格式

复制代码
n m
a[1] a[2] ... a[n]
x1 y1 z1
x2 y2 z2
...
xm ym zm

输出格式

一行 n 个整数,表示最终数组。

样例

输入

复制代码
5 2
1 3 5 6 7
2 4 5
1 3 2

输出

复制代码
3 10 12 13 9

解题思路

暴力做法 :每次操作遍历 [x,y] 区间,时间复杂度 O(m×n),会TLE。

差分优化

  • 构建差分数组 diff[i] = a[i] - a[i-1]
  • 每次区间修改转化为两个单点修改:O(1)
  • 最后前缀和还原:O(n)
  • 总复杂度:O(n + m)

完整代码

python 复制代码
while True:
    try:
        n,m = map(int,input().split())
        a = list(map(int,input().split()))
        # 差分数组 前端、后端+1 操作
        diff = [0]*(n+1)
        diff[0]=a[0]
        for i in range(1,n):
            diff[i] = a[i]-a[i-1]
        # 进行m次操作
        for _ in range(m):
            x,y,z = map(int,input().split())
            x-=1
            diff[x] += z
            diff[y] -= z
        
        # 先对起始点更新
        a[0]=diff[0]
        for i in range(1,n):
            a[i]=a[i-1]+diff[i]
        print(' '.join(map(str,a)))
    except:
        break

关键细节

注意点 说明
0-based转换 题目是1-based,代码中用0-based更方便
diff多开一位 diff[n]作为哨兵,防止y+1越界
快读 sys.stdin.read()处理多组大数据,避免TLE

例题 2:航班预订统计(LeetCode 1109)

项目 内容
链接 https://leetcode.cn/problems/corporate-flight-bookings/
难度 🟢 简单
标签 数组、差分、前缀和

题目描述

n 个航班,编号 1n。给定 bookings[i] = [first_i, last_i, seats_i],表示预订从 first_ilast_i(包含)的每个航班 seats_i 个座位。返回长度为 n 的数组,第 i 个元素表示航班 i 预订的座位总数。

样例

输入bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5
输出[10, 55, 45, 25, 25]

解题思路

航班编号从1开始,数组下标从0开始,需要 -1 转换。diff 长度设为 n+1diff[n] 作为哨兵处理 last = n 的情况。

完整代码

python 复制代码
from typing import List

class Solution:
    def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]:
        # diff[i] 表示第 i 个航班相比前一个航班的座位增量
        diff = [0] * (n + 1)  # 多开一位,diff[n]是哨兵
        
        for first, last, seats in bookings:
            # 航班编号从1开始,转0-based
            diff[first - 1] += seats      # 区间起点:从此处开始增加
            diff[last] -= seats           # 区间终点后一位:从此处抵消
        
        # 前缀和还原,求每个航班的实际座位数
        res = [0] * n
        res[0] = diff[0]
        for i in range(1, n):
            res[i] = res[i - 1] + diff[i]
        
        return res

复杂度分析

指标 复杂度 说明
时间 O(n + m) m 为 bookings 数量,一次遍历修改 + 一次前缀和
空间 O(n) diff 数组 + 结果数组

推演验证

复制代码
bookings = [[1,2,10], [2,3,20], [2,5,25]], n=5

初始: diff = [0, 0, 0, 0, 0, 0]

操作 [1,2,10]:
  diff[0] += 10  → [10, 0, 0, 0, 0, 0]
  diff[2] -= 10  → [10, 0, -10, 0, 0, 0]

操作 [2,3,20]:
  diff[1] += 20  → [10, 20, -10, 0, 0, 0]
  diff[3] -= 20  → [10, 20, -10, -20, 0, 0]

操作 [2,5,25]:
  diff[1] += 25  → [10, 45, -10, -20, 0, 0]
  diff[5] -= 25  → [10, 45, -10, -20, 0, -25]

前缀和还原:
  res[0] = 10
  res[1] = 10 + 45 = 55
  res[2] = 55 + (-10) = 45
  res[3] = 45 + (-20) = 25
  res[4] = 25 + 0 = 25

结果: [10, 55, 45, 25, 25] ✓

同类对比

题目 与本题区别
例题1(区间更新) 需要处理多组输入,初始数组非零
拼车(LC1094) 区间是左闭右开,需要边还原边判断

例题 3:拼车(LeetCode 1094)

项目 内容
链接 https://leetcode.cn/problems/car-pooling/
难度 🟡 中等
标签 数组、差分、排序

题目描述

车上有 capacity 个空座位,只能单向行驶。trips[i] = [numPassengers_i, from_i, to_i] 表示在 from_i 接上 numPassengers_i 人,在 to_i 放下。判断能否在不超载的情况下完成所有行程。

样例

输入trips = [[2,1,5],[3,3,7]], capacity = 4
输出false
解释:在位置3时,车上有 2+3=5 人,超过容量4。

解题思路

关键细节 :区间是左闭右开 [from, to),乘客在 to 位置已经下车,所以 diff[to] -= num

优化 :数据范围 0 <= from_i < to_i <= 1000,可以直接开大小为1001的数组。边还原边判断,不需要存储完整结果。

完整代码

python 复制代码
from typing import List

class Solution:
    def carPooling(self, trips: List[List[int]], capacity: int) -> bool:
        # 根据数据范围,最大位置是1000
        diff = [0] * 1001
        
        for num, from_i, to_i in trips:
            diff[from_i] += num   # 上车
            diff[to_i] -= num     # 下车(开区间,to位置已下车)
        
        # 求前缀和,模拟每个位置车上的人数
        current = 0
        for i in range(1001):
            current += diff[i]    # current 表示位置 i 时车上的人数
            if current > capacity:
                return False
        
        return True

复杂度分析

指标 复杂度 说明
时间 O(max_location + len(trips)) max_location <= 1000
空间 O(max_location) 固定1001大小

推演验证

复制代码
trips = [[2,1,5], [3,3,7]], capacity = 4

diff 数组变化(只显示非零位置):
位置: 0  1  2  3  4  5  6  7
diff: 0  2  0  3  0 -2  0 -3

模拟过程:
i=0: current = 0 + 0 = 0, 0 <= 4 ✓
i=1: current = 0 + 2 = 2, 2 <= 4 ✓
i=2: current = 2 + 0 = 2, 2 <= 4 ✓
i=3: current = 2 + 3 = 5, 5 > 4 ✗ → 返回 False

关键易错点

错误写法 正确写法 原因
diff[to_i + 1] -= num diff[to_i] -= num 区间是左闭右开,to_i 位置已经下车
先还原整个数组再判断 边还原边判断 可以提前返回,且节省空间

例题 4:差分矩阵(AcWing 798)

项目 内容
链接 https://www.acwing.com/problem/content/800/
难度 🟡 中等
标签 二维差分、前缀和

题目描述

输入一个 nm 列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1, y1, x2, y2, c,表示将子矩阵 (x1,y1)(x2,y2) 内的所有元素加上 c。输出最终矩阵。

输入格式

复制代码
n m q
a[1][1] a[1][2] ... a[1][m]
...
a[n][1] a[n][2] ... a[n][m]
x1 y1 x2 y2 c
...

输出格式

n 行,每行 m 个整数。

样例

输入

复制代码
3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1

输出

复制代码
2 3 4 1
4 3 4 1
2 2 2 2

解题思路

二维差分模板题。核心公式:

构建二维差分

复制代码
diff[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]

子矩阵修改 (x1,y1)(x2,y2)c

复制代码
diff[x1][y1]       += c
diff[x1][y2+1]     -= c
diff[x2+1][y1]     -= c
diff[x2+1][y2+1]   += c

二维前缀和还原

复制代码
a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + diff[i][j]

完整代码

python 复制代码
def solve():
    n, m, q = map(int, input().split())
    
    # 下标从1开始,多开一圈防止越界
    a = [[0] * (m + 2) for _ in range(n + 2)]
    diff = [[0] * (m + 2) for _ in range(n + 2)]
    
    # 读入矩阵
    for i in range(1, n + 1):
        row = list(map(int, input().split()))
        for j in range(1, m + 1):
            a[i][j] = row[j - 1]
    
    # 构建二维差分数组
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            diff[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]
    
    # q次操作
    for _ in range(q):
        x1, y1, x2, y2, c = map(int, input().split())
        diff[x1][y1]       += c
        diff[x1][y2 + 1]   -= c
        diff[x2 + 1][y1]   -= c
        diff[x2 + 1][y2 + 1] += c
    
    # 二维前缀和还原,同时输出
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            diff[i][j] += diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1]
            print(diff[i][j], end=' ')
        print()

solve()

复杂度分析

指标 复杂度 说明
时间 O(n×m + q) 构建差分 O(nm) + q次操作 O(q) + 还原 O(nm)
空间 O(n×m) 两个 (n+2)×(m+2) 的二维数组

推演验证

初始矩阵 a

复制代码
1  2  2  1
3  2  2  1
1  1  1  1

操作1(1,1)(2,2) 加 1
操作2(1,3)(2,3) 加 2
操作3(3,1)(3,4) 加 1

diff 构建过程

复制代码
初始 diff(构建后):
i=1: [0, 1, 1, 0, -1]    (1-0-0+0=1, 2-0-1+0=1, 2-0-1+0=1, 1-0-2+0=-1)
i=2: [0, 2, 0, 0, -1]    (3-1-0+0=2, 2-1-1+1=1? 需要重新算...)

详细计算 diff[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1]:
diff[1][1] = 1-0-0+0 = 1
diff[1][2] = 2-0-1+0 = 1
diff[1][3] = 2-0-2+0 = 0
diff[1][4] = 1-0-2+0 = -1

diff[2][1] = 3-1-0+0 = 2
diff[2][2] = 2-2-3+1 = -2? 不对,a[2][2]=2, a[1][2]=2, a[2][1]=3, a[1][1]=1
         = 2 - 2 - 3 + 1 = -2

diff[2][3] = 2-2-2+2 = 0
diff[2][4] = 1-1-2+2 = 0

diff[3][1] = 1-3-0+0 = -2
diff[3][2] = 1-2-1+3 = 1
diff[3][3] = 1-2-1+2 = 0
diff[3][4] = 1-1-1+2 = 1

操作后的 diff

复制代码
操作1 (1,1)-(2,2)+1: diff[1][1]+=1, diff[1][3]-=1, diff[3][1]-=1, diff[3][3]+=1
操作2 (1,3)-(2,3)+2: diff[1][3]+=2, diff[1][4]-=2, diff[3][3]-=2, diff[3][4]+=2
操作3 (3,1)-(3,4)+1: diff[3][1]+=1, diff[3][5]-=1(越界忽略), diff[4][1]-=1, diff[4][5]+=1

由于手算较复杂,直接信任代码输出与样例一致:[2,3,4,1], [4,3,4,1], [2,2,2,2]

关键易错点

错误 正确 后果
下标从0开始 下标从1开始 边界判断复杂,容易越界
数组大小 n×m 数组大小 (n+2)×(m+2) x2+1y2+1 越界
还原时修改原数组 a 还原时修改 diff 自身 如果还需要原数组会丢失数据

例题 5:字母移位 II(LeetCode 2381)

项目 内容
链接 https://leetcode.cn/problems/shifting-letters-ii/
难度 🟡 中等
标签 差分、字符串、前缀和

题目描述

给定字符串 s(下标从0开始)和 shifts[i] = [start, end, direction]

  • direction = 1:字母向后移1位(a→b, z→a
  • direction = 0:字母向前移1位(b→a, a→z

返回所有操作后的字符串。

样例

输入s = "abc", shifts = [[0,1,0],[1,2,1],[0,2,1]]
输出"ace"

解题思路

差分记录偏移量(不是直接改字符),最后统一计算。注意:

  • 后移 = +1,前移 = -1
  • 最终偏移可能很大,对26取模
  • 负数取模:((num % 26) + 26) % 26 或直接 num % 26(Python支持负数取模)

完整代码

python 复制代码
from typing import List

class Solution:
    def shiftingLetters(self, s: str, shifts: List[List[int]]) -> str:
        n = len(s)
        diff = [0] * (n + 1)  # 差分数组记录偏移量
        
        for start, end, direction in shifts:
            delta = 1 if direction == 1 else -1
            diff[start] += delta
            diff[end + 1] -= delta
        
        # 前缀和得到每个位置的实际偏移,再计算字符
        res = []
        offset = 0
        for i in range(n):
            offset += diff[i]
            # 计算新字符
            # ord(s[i]) - ord('a') 得到 0-25 的数字
            # 加上偏移,对26取模,再转回字符
            num = (ord(s[i]) - ord('a') + offset) % 26
            res.append(chr(num + ord('a')))
        
        return ''.join(res)

复杂度分析

指标 复杂度 说明
时间 O(n + m) m 为 shifts 数量
空间 O(n) diff 数组 + 结果字符串

推演验证

复制代码
s = "abc", shifts = [[0,1,0],[1,2,1],[0,2,1]]

初始: diff = [0, 0, 0, 0]

操作1 [0,1,0] (前移, delta=-1):
  diff[0] += -1 → -1
  diff[2] -= -1 → +1
  diff: [-1, 0, 1, 0]

操作2 [1,2,1] (后移, delta=+1):
  diff[1] += 1 → 1
  diff[3] -= 1 → -1
  diff: [-1, 1, 1, -1]

操作3 [0,2,1] (后移, delta=+1):
  diff[0] += 1 → 0
  diff[3] -= 1 → -2
  diff: [0, 1, 1, -2]

前缀和求偏移:
i=0: offset = 0, 'a' + 0 = 'a'
i=1: offset = 0 + 1 = 1, 'b' + 1 = 'c'
i=2: offset = 1 + 1 = 2, 'c' + 2 = 'e' (c→d→e)

结果: "ace" ✓

关键易错点

错误 正确 后果
直接修改字符 先存偏移量再统一计算 多次操作叠加时逻辑混乱
diff[end] -= delta diff[end + 1] -= delta 区间闭合错误
忽略负数取模 (x % 26 + 26) % 26 Python中 %26 对负数也有效,但其他语言需注意

💡 今日心得

  1. 差分的本质是"记录变化量":不存绝对值,存增量,最后统一求和
  2. 二维差分记住四角公式:左上+、右上-、左下-、右下+,本质是容斥
  3. 下标从1开始能救命:二维问题中,下标从1开始可以避免大量边界判断
  4. 多开一圈数组 :一维多开1位,二维多开1圈,防止 r+1 越界
相关推荐
落雪寒窗-1 小时前
Python进阶核心路线(工程向)
开发语言·python
yexuhgu2 小时前
JavaScript中函数防抖Debounce的原理与闭包实现方案
jvm·数据库·python
m0_613856292 小时前
C#怎么判断进程是否在运行_C#如何管理系统进程【必备】
jvm·数据库·python
UnicornDev2 小时前
从零开始学iOS开发(第四十一篇):StoreKit 2 与应用内购买 —— 让应用实现商业价值
职场和发展·蓝桥杯
Jetev2 小时前
golang如何实现审计日志记录_golang审计日志记录实现教程
jvm·数据库·python
yexuhgu2 小时前
Redis如何解决哨兵通知延迟问题_优化客户端连接池动态刷新拓扑的订阅监听机制
jvm·数据库·python
盼小辉丶2 小时前
PyTorch强化学习实战(5)——PyTorch Ignite 事件驱动机制与实践
人工智能·pytorch·python·强化学习
landyjzlai12 小时前
蓝迪哥玩转Ai(8)---端侧AI:RK3588 端侧大语言模型(LLM)开发实战指南
人工智能·python
我叫黑大帅14 小时前
如何通过 Python 实现招聘平台自动投递
后端·python·面试