差分

2022/01/15 Algorithms 共 3407 字,约 10 分钟

差分

前缀和是元素的累加,而差分则是相邻两元素的差;前缀和用来计算整块元素的和,而差分则用来实现整块元素的加或减(例如将a[m]到a[n]都+1);

同样,差分也可以化区间计算为点计算,减小时间复杂度。

diff[n]的含义,便是原始数组中当前项减去前一项的差。 即: diff[n] = a[n] - a[n-1] ;

主要用于针对某个区间+x的操作复杂度降低,可以o(1)对某个区间增加复杂度,然后可以根据差分数组反推原数组,叫做回收。 其实就是求diff的前缀和 就是回收啦

前缀和知识请参考: 前缀和

有区间更新题目应该考虑差分。

模板

一维差分

定义: diff[n] = a[n] - a[n-1] ;

因此想要[l,r]上+x, 是diff[l]+=x, diff[r+1]-=x,落到diff数组上就是l和r+1

diff数组长度应该n+1,diff[0]=0

class Solution:
    def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]:
        diff = [0] * (n+1)
        for a, b, c in bookings: # [a,b]区间都+c
            diff[a - 1] += c
            diff[b] -= c
        ans = [0] * n
        ans[0] = diff[0]
        for i in range(1, n):
            ans[i] = ans[i - 1] + diff[i]
        return ans

差分数组还原成原数组

    ans[0] = diff[0]
    for i in range(1, n):
        ans[i] = ans[i - 1] + diff[i]

二维差分

二维数组diff上某个位置加1会影响当前及右下角方向的点的前缀和计算。

想让矩阵[i,j]到[m,n]整体加x,对差分数组应该有如下操作

diff[i][j] += x (此时求前缀和数组的话从[i,j]一直到右下角都是加了x的)
diff[m + 1][j] -= x, diff[i][n + 1] -= x (此时结果接近正确,只有[m+1,n+1]到右下角都是-x的)
diff[m + 1][n + 1] += x ([m+1,n+1]到右下角都加x,得到正确结果)


模板
# 二维差分矩阵 记录邮票放置情况:
def diffInsert(x1, y1, x2, y2, c=1):
  diff[x1][y1] += c       # 左上角+1
  diff[x1][y2+1] -= c     # 右上角右侧位置-1
  diff[x2+1][y1] -= c     # 左下角下侧位置-1
  diff[x2+1][y2+1] += c   # 右下角对角处(行列均+1处)+1
        

二维差分数组的长宽应该是m+2, n+2。

如图,红色部分是(i,j) -> (m,n)。

image.png

差分数组还原成原数组: 和计算当前位置前缀和逻辑一样,(i,j)左边+上边的-左上的+(i,j)的,记住要更新diff

# 从二维差分矩阵diff中还原出对应的计数矩阵:运用二维前缀和
for i in range(1, m+1):
	for j in range(1, n+1):
		diff[i][j] = diff[i][j-1] + diff[i-1][j] - diff[i-1][j-1] + diff[i][j]

Python一维和二维模板

class Diff:
    def __init__(self, n):
        self.n = n
        self.diff = [0] * (n + 1)

    # 给[l,r] + val
    def add(self, l, r, val):
        self.diff[l] += val
        self.diff[r + 1] -= val

    # 差分数组还原成原数组
    def restore(self):
        ans = [0] * self.n
        ans[0] = self.diff[0]
        for i in range(1, self.n):
            ans[i] = ans[i - 1] + self.diff[i]
        return ans


class Diff2:
    def __init__(self, m, n):
        self.m = m
        self.n = n
        self.diff = [[0] * (n + 2) for _ in range(m + 2)]

    # 给[x,y] -> [x1,y1] + v
    def add_diff(self, x, y, x1, y1, v):
        self.diff[x + 1][y + 1] += v
        self.diff[x1 + 2][y1 + 2] += v
        self.diff[x1 + 2][y + 1] -= v
        self.diff[x + 1][y1 + 2] -= v

    # 差分数组还原成原数组
    def restore(self):
        m, n = self.m, self.n
        ans = [[0] * n for _ in range(m)]
        diff = self.diff
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                # 差分数组还原成原数组: 和计算当前位置前缀和逻辑一样,(i,j)左边+上边的-左上的+(i,j)的,记住要更新diff
                diff[i][j] = diff[i][j] + diff[i - 1][j] + diff[i][j - 1] - diff[i - 1][j - 1]
                ans[i - 1][j - 1] = diff[i][j]
        return ans

应用

一维差分

1109. 航班预订统计 有一份航班预订表 bookings ,表中第 i 条预订记录 bookings[i] = [firsti, lasti, seatsi] 意味着在从 firsti 到 lasti (包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位。

请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。

实际上就是区间增加x,然后根据还原出每个点的值。一维差分

class Solution:
    def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]:
        diff = [0] * (n+1)
        for a, b, c in bookings:
            diff[a - 1] += c
            diff[b] -= c
        ans = [0] * n
        ans[0] = diff[0]
        for i in range(1, n):
            ans[i] = ans[i - 1] + diff[i]
        return ans

二维差分

2132. 用邮票贴满网格图 给你一个 m x n 的二进制矩阵 grid ,每个格子要么为 0 (空)要么为 1 (被占据)。

给你邮票的尺寸为 stampHeight x stampWidth 。我们想将邮票贴进二进制矩阵中, 看能不能贴满,然后无遗漏点的,邮票之间可重合。


class Solution:
    def possibleToStamp(self, grid: List[List[int]], stampHeight: int, stampWidth: int) -> bool:
        m, n = len(grid), len(grid[0])
        pre = [[0] * (n + 1) for _ in range(m + 1)]
        diff = [[0] * (n + 2) for _ in range(m + 2)]

        # x range in [0,m)
        def get_pre(x, y, x1, y1):
            return pre[x1 + 1][y1 + 1] + pre[x][y] - pre[x1 + 1][y] - pre[x][y1 + 1]

        # x range in [0,m)
        def add_diff(x, y, x1, y1, v):
            diff[x + 1][y + 1] += v
            diff[x1 + 2][y1 + 2] += v
            diff[x1 + 2][y + 1] -= v
            diff[x + 1][y1 + 2] -= v

        # 遍历grid, 如果为0,则看能不能加邮票,如果能加邮票,就区间加上1, >0表示有邮票覆盖
        for i in range(m):
            for j in range(n):
                pre[i + 1][j + 1] = pre[i + 1][j] + pre[i][j + 1] - pre[i][j] + grid[i][j]
                if grid[i][j] == 1: continue
                x, y = i - stampHeight + 1, j - stampWidth + 1
                if x >= 0 and y >= 0 and get_pre(x, y, i, j) == 0:
                    add_diff(x, y, i, j, 1)
        # 根据diff还原邮票,如果没被邮票覆盖,并且原来位置需要有邮票,return False
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                diff[i][j] = diff[i][j] + diff[i - 1][j] + diff[i][j - 1] - diff[i - 1][j - 1]
                if diff[i][j] == 0 and grid[i - 1][j - 1] == 0:
                    return False
        return True

Search

    Table of Contents