前缀和,差分--灵神题单

前缀和与差分

前缀和和差分都是将O(n)操作优化为O(1)的算法。这两个算法都是用于对数组连续子数组的操作,前缀和用于对子数组求和,差分用于对子数组进行统一的加。一维的前缀和,差分是比较容易理解的,而二维的最好是辅助画图帮助理解。差分和前缀和是存在联系的,差分还原就是前缀和。

注意:当在画图理解二维前缀和时,要注意数组元素是代表线还是格子,不然容易得到错误的结论(如果是线的话,可能会得到s[x2][y2]-s[x1][y2]-s[x2][y1]+s[x1][y1],但实际上s[x1][y2],s[x2][y1]是包含了我们要求的矩阵的边界部分,这样并不对,而应该是s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1])

一维前缀和

模板:sum[0]=a[0],sum[i]=sum[i-1]+a[i]

注意有些题是看起来能用滑动窗口的,但要注意滑动窗口必须满足单调

力扣53

一维前缀和有时候可以优化为空间O(1)(这个题还可以用一维dp解决)

class Solution {
public:
    int maxSubArray(vector& nums) {
        int ans=-1e6,minpre=0,pre=0;
        for(int i=0;i

力扣1749

子数组的和等于前缀和的差,最大子数组和等于前缀和的最大值减去最小值

(可能存在疑问,万一最大值在前最小值在后是不是不符合?这里是绝对值,并不存在这样的问题)

class Solution {
public:
    int maxAbsoluteSum(vector& nums) {
        int n=nums.size();
        int mmax=0,mmin=0;
        int sum=0;
        for(int i=0;i

一维前缀和和哈希表的结合

力扣525

class Solution {
public:
    int findMaxLength(vector& nums) {
        //hashmap加前缀和。当和为0时,0和1的数目相等。
        //如果i到j的0和1的数目相等那么i到j的前缀和相等
        int sum=0,ans=0;
        int n=nums.size();
        mapm;
        m[0]=-1;
        for(int i=0;i

力扣560

​
class Solution {
public:
    int subarraySum(vector& nums, int k) {
        //思路:刚开始以为是用滑动窗口,但是存在负数,所以用前缀和。题目是和为k,我们可以转换为i之前存在多少sum[i]-k即可。(sum[j]-sum[i]=k,移项就得到了上述思路)
        //注意:这里的子数组是指连续子数组
       mapm;
       m[0]=1;
       int sum=0,ans=0;
       int n=nums.size();
        for(int i=0;i

二维前缀和

模板:s[i][j] = s[i - 1][j] + s[i][j - 1 ] + a[i] [j] - s[i - 1][j - 1]

s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]

注意边界

模板题 力扣1314

#include 
#include 

using namespace std;

class Solution {
public:
    vector> matrixBlockSum(vector>& mat, int k) {
        int n = mat.size();
        int m = mat[0].size();
        // 计算二维前缀和数组
        vector> sum(n + 1, vector(m + 1, 0));
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                sum[i][j] = sum[i - 1][j] + sum[i][j - 1] + mat[i - 1][j - 1] - sum[i - 1][j - 1];
            }
        }
        
        // 计算每个元素的区域和
        vector> ans(n, vector(m, 0));
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {  
                int x1 = max(0, i - k) + 1;
                int y1 = max(0, j - k) + 1;
                int x2 = min(n, i + k + 1);
                int y2 = min(m, j + k + 1);
                
                ans[i][j] = sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];
            }
        }
        
        return ans;
    }
};

力扣221

这个题可以用二维dp来做,二维dp的动态转移方程的迷惑点在于那个min,这个是确保新形成的正方形被其他正方形包含

二维前缀和的关键在于如何判断正方形全是1:前缀和等于面积

二维dp

class Solution {
public:
    int maximalSquare(vector>& matrix) {
        int ans=0;
        if(matrix.size()==0)
            return ans;
        int m=matrix.size(),n=matrix[0].size();
        vector>dp(m,vector(n));
        for(int i=0;i

二维前缀和

class Solution {
public:
    int maximalSquare(vector>& matrix) {
        int n = matrix.size();
        int m = matrix[0].size();
        vector> sum(n+1, vector(m+1, 0));

        for(int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                sum[i+1][j+1] = sum[i+1][j] + sum[i][j+1] + matrix[i][j] - '0' - sum[i][j];
            }
        }

        int ret = 0;
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                for (int k = 1; k <= min(i, j); ++k) {
                    int t = sum[i][j] - sum[i-k][j] - sum[i][j-k] + sum[i-k][j-k];
                    if (t == k * k) ret = max(ret, t);
                }
                
            }
        }
        return ret;
    }
};

一维差分

形成差分数组是右减左,还原数组是右加左

力扣 2848 模板题

class Solution {
public:
    int numberOfPoints(vector>& nums) {
        vectord(102);
        int n=nums.size();
        for(int i=0;ia(101);
        int cnt=0,s=0;
        for(int i=1;i<101;i++){
            d[i]+=d[i-1];
            if(d[i]>0)
                cnt++;
        }
        return cnt;

    }
};

力扣 1094

可能会有疑惑为什么这里对差分数组操作时不是to+1。

你细品,当我们思考to站的人时是不是并没有算上从from站上的人,所以实际上是[from,to-1]闭区间上加

class Solution {
public:
//思路:一开始想到的是对一段旅程之间的站点都加上乘客,但是好多O(n)大概会超时,一段上边加可以考虑差分
    bool carPooling(vector>& trips, int capacity) {
        vectord(1001);
        int n=trips.size();
        for(int i=0;icapacity)
                return false;
        }
        return true;
    }
};

二维差分

模板:b[i][j] = a[i][j] − a[i − 1][j] − a[i][j − 1] + a[i −1 ][j − 1]

b[x1][y1] += c, b[x2 + 1][y1] -= c, b[x1][y2 + 1] -= c, b[x2 + 1][y2 + 1] += c;

注意边界

力扣2536 模板题

#include 
using namespace std;

class Solution {
public:
    vector> rangeAddQueries(int n, vector>& queries) {
        vector> d(n + 1, vector(n + 1, 0);
        for (int i = 0; i < queries.size(); i++) {
            int x1 = queries[i][0], y1 = queries[i][1];
            int x2 = queries[i][2], y2 = queries[i][3];
            d[x1][y1] += 1;
            d[x2 + 1][y1] -= 1;
            d[x1][y2 + 1] -= 1;
            d[x2 + 1][y2 + 1] += 1;
        }
        vector> ans(n, vector(n, 0));
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i > 0) d[i][j] += d[i - 1][j];
                if (j > 0) d[i][j] += d[i][j - 1];
                if (i > 0 && j > 0) d[i][j] -= d[i - 1][j - 1];
                ans[i][j] = d[i][j];
            }
        }
        return ans;
    }
};

力扣2132

class Solution {
public:
    // 如何保证不覆盖任何被占据的格子?确保区域内和为0,需要使用二维前缀和
    // 如何标记被邮票覆盖的格子?二维差分。
    bool possibleToStamp(vector>& grid, int stampHeight,int stampWidth) {
        int m = grid.size(), n = grid[0].size();
        vector> s(m + 1, vector(n + 1));
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                s[i + 1][j + 1] = s[i + 1][j] + s[i][j + 1] - s[i][j] + grid[i][j];
            }
        }
        vector>d(m+2,vector(n+2));
        for(int i=stampHeight;i<=m;i++){
            for(int j=stampWidth;j<=n;j++){
                int i1=i-stampHeight+1;
                int j1=j-stampWidth+1;
                if(s[i][j]-s[i][j1-1]-s[i1-1][j]+s[i1-1][j1-1]==0){
                     d[i1][j1]++;
                    d[i1][j + 1]--;
                    d[i + 1][j1]--;
                    d[i + 1][j + 1]++;
                }
            }
        }
        for(int i=0;i

总结

到这里前缀和和差分的基础学习就结束,这里只是结合题目描述了基础的应用,原理需要自己去画图理解,因为单纯去看别人给你讲述原理终究不如自己思考来得扎实。若是存在错误,希望大家多多包含并指出。

你可能感兴趣的:(数据结构,算法)