前缀和与差分

leetCode常用数据结构题单

1. 前缀和

1.1 前缀和基础

模板区域和检索-数组不可变
记数组的前缀和\(sums_i\)为下标\([0,i)\)的子数组的和

class NumArray {
public:
    vector<int> sums;
    NumArray(vector<int>& nums) {
        int n = nums.size();
        sums.resize(n+1);
        for(int i = 0; i < n; i++)
            sums[i+1] = nums[i]+sums[i];
    }
    
    int sumRange(int left, int right) {
        return sums[right+1]-sums[left];
    }
};

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray* obj = new NumArray(nums);
 * int param_1 = obj->sumRange(left,right);
 */

1.2 前缀和与哈希表

通常要用到「枚举右,维护左」的技巧。
模板560.和为k的子数组
题目简述:给你一个整数数组\(nums\)和一个整数\(k\),请你统计并返回该数组中和为\(k\)的子数组(连续非空序列)的个数。
枚举子数组的右端点\(r\),记数组的前缀和\(sums_i\)为下标\([0,i)\)的子数组的和,则对于此右端点,满足条件的左端点应满足\(sums_{r+1} - sums_l = k\),用哈希表\(cnt\)记录\(sums\)出现的次数,枚举\(r\)的过程中累加\(cnt[sums_{r+1} - k]\)即可求得和为k的子数组总数。

int subarraySum(vector<int>& nums, int k) {
    int n = nums.size(), sums = 0, ans = 0;
    map<int, int> cnt;
    cnt[0]++;
    for(int i = 0; i < n; i++){
        sums += nums[i];
        ans += cnt[sums - k];
        cnt[sums]++;
    }
    return ans;
}

1.3 距离和

通常需要结合二分查找目标值在数组中的位置。
模板2602.使数组元素全部相等的最少操作次数
题目简述:求数组\(nums\)中每个数与目标值\(queries_i\)的差的绝对值之和。
将数组\(nums\)按非降序排列,记数组的前缀和\(sums_i\)为下标\([0,i)\)的子数组的和,找到第一个不小于\(queries_i\)的位置\(temp\),则数组下标为\([0,temp)\)的部分均小于\(queries_i\),这部分的差的绝对值之和为\(temp*queries_i - sums_{temp}\);同理数组下标为\([temp, n)\)的部分,和为\(sums_n - sums_{temp} - (n-temp)*queries_i\)

int lower_bound(vector<int>& nums, int q){
    // 找到nums[i]>=q的第一个位置
    int l = 0, r = nums.size();
    while(l < r){
        int mid = (l+r)/2;
        if(nums[mid] < q) l = mid+1;
        else r = mid;
    }
    return l;
}
vector<long long> minOperations(vector<int>& nums, vector<int>& queries) {
    int n = nums.size(), m = queries.size();
    sort(nums.begin(), nums.end());
    vector<long long> ans(m), sums(n+1);
    for(int i = 0; i < n; i++) sums[i+1] = sums[i] + nums[i];
    for(int i = 0; i < m; i++){
        int temp = lower_bound(nums, queries[i]);
        ans[i] = (long long)temp*queries[i] - sums[temp] + 
            sums[n] - sums[temp] - (long long)(n-temp)*queries[i];
    }
    return ans;
}

1.4 前缀异或和

通常需要用二进制表示元素出现的奇偶次。
例题1371.每个元音包含偶数次的最长子字符串
题目简述:求每个元音字母在子字符串中都恰好出现了偶数次的最长子字符串的长度。
同样需要用到「枚举右,维护左」的技巧。通过5个\(sums_i\)分别记录5个元音字母在子数组\([0,i)\)中出现的次数,对于第\(j\)个元音字母,若其在子数组\([l,r)\)中出现了偶数次,则\(sums_r\)\(sums_l\)同奇偶。则可以通过二进制数的第\(j\)位为0或1,表示第\(j\)个元音字母出现了偶数次或奇数次,剩下的处理同前缀和与哈希表部分。

int findTheLongestSubstring(string s) {
    string yuanyin = "aeiou";
    int ans = 0;
    vector<int> sums(5);
    map<int, int> cnt;
    cnt[0] = 0;
    for(int i = 0; i < s.size(); i++){
        int temp = 0;
        for(int j = 0; j < 5; j++) {
            if(s[i] == yuanyin[j]) sums[j]++;
            temp += (sums[j]%2)<<j;
        }
        if(cnt.find(temp) != cnt.end()) ans = max(ans, i-cnt[temp]+1);
        else cnt[temp] = i+1;
    }
    return ans;
}

1.5 二维前缀和

由一维线性区域的和推广到二维矩形区域的和,一些较复杂的题目需要通过哈希表对矩形的坐标值重新赋值。
模板304.二维区域和检索-矩阵不可变

class NumMatrix {
public:
    vector<vector<int>> sums;
    NumMatrix(vector<vector<int>>& matrix) {
        int n = matrix.size(), m = matrix[0].size();
        sums.resize(n+1, vector<int>(m+1));
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
                sums[i+1][j+1] = sums[i][j+1] + sums[i+1][j] 
                                 - sums[i][j] + matrix[i][j];
    }
    
    int sumRegion(int row1, int col1, int row2, int col2) {
        return sums[row2+1][col2+1] - sums[row2+1][col1] 
                - sums[row1][col2+1] + sums[row1][col1];
    }
};

/**
 * Your NumMatrix object will be instantiated and called as such:
 * NumMatrix* obj = new NumMatrix(matrix);
 * int param_1 = obj->sumRegion(row1,col1,row2,col2);
 */

2. 差分

差分与前缀和的关系,类似导数与积分的关系。数组\(a\)的差分的前缀和就是数组\(a\)

2.1 一维差分

模板1893.检查是否区域内所有整数都被覆盖

bool isCovered(vector<vector<int>>& ranges, int left, int right) {
    vector<int> dec(52), sums(52);
    for(int i = 0; i < ranges.size(); i++)
        dec[ranges[i][0]]++, dec[ranges[i][1]+1]--;
    for(int i = 0; i <= 50; i++) sums[i+1] = sums[i] + dec[i];
    for(int i = left; i <= right; i++)
        if(sums[i+1] <= 0) return false;
    return true;
}

2.2 二维差分

基本思路与二维前缀和相同。
例题LCP 74.最强祝福力场

int fieldOfGreatestBlessing(vector<vector<int>>& forceField) {
    // 将坐标重新赋值
    map<double, int> xs, ys;
    for(auto & it : forceField){
        double x = it[0], y = it[1], d = it[2];
        xs[x-d/2]++; xs[x+d/2]++;
        ys[y-d/2]++; ys[y+d/2]++;
    }

    int sumx = 0, sumy = 0;
    vector<double> newx(xs.size()), newy(ys.size());
    for(auto &x : xs) 
        newx[sumx] = x.first, xs[x.first] = sumx++;
    for(auto &y : ys) 
        newy[sumy] = y.first, ys[y.first] = sumy++;
    
    // 计算差分与前缀和
    vector<vector<int>> dec(sumx+1, vector<int>(sumy+1)),
                        sums(sumx+1, vector<int>(sumy+1));
    for(auto & it : forceField){
        double x = it[0], y = it[1], d = it[2];
        dec[xs[x-d/2]][ys[y-d/2]]++; 
        dec[xs[x+d/2]+1][ys[y+d/2]+1]++;
        dec[xs[x-d/2]][ys[y+d/2]+1]--; 
        dec[xs[x+d/2]+1][ys[y-d/2]]--;
    }
    int ans = sums[0][0] = dec[0][0];
    for(int x = 1; x < sumx; x++) 
        sums[x][0] = sums[x-1][0] + dec[x][0], ans = max(ans, sums[x][0]);
    for(int y = 1; y < sumy; y++)
        sums[0][y] = sums[0][y-1] + dec[0][y], ans = max(ans, sums[0][y]);
    
    for(int x = 1; x < sumx; x++) 
        for(int y = 1; y < sumy; y++)
            sums[x][y] = sums[x-1][y] + sums[x][y-1] 
                - sums[x-1][y-1] + dec[x][y],
            ans = max(ans, sums[x][y]);
    return ans;
}
posted @ 2025-06-11 22:14  zerolt  阅读(21)  评论(0)    收藏  举报