【每日算法】Day 13-1:前缀和与差分数组——区间统计与更新的高效技巧(C++实现)

征服区间操作的核心技术!今日深入解析前缀和与差分数组的底层原理与实战应用,覆盖子数组和统计、区间批量更新等高频场景,彻底掌握O(1)复杂度查询与更新的优化艺术。

一、前缀和核心思想

前缀和(Prefix Sum) 是一种通过预处理数组实现快速区间查询的技术,核心特性:

  1. 预处理:构建前缀和数组prefix,其中prefix[i]表示原数组前i项的和

  2. 快速查询:区间和计算转化为两次前缀和相减(sum[l, r] = prefix[r] - prefix[l-1]

  3. 扩展性:支持多维扩展(如二维矩阵区域和)

适用场景:

  • 频繁查询区间和/区间统计量

  • 多维空间区域和计算

  • 结合哈希表优化特定和问题


二、前缀和模板与实现

一维前缀和(LeetCode 303)
class NumArray {
private:
    vector<int> prefix;
public:
    NumArray(vector<int>& nums) {
        prefix.resize(nums.size() + 1, 0);
        for (int i = 0; i < nums.size(); ++i) {
            prefix[i+1] = prefix[i] + nums[i];
        }
    }
    
    int sumRange(int left, int right) {
        return prefix[right+1] - prefix[left];
    }
};
二维前缀和(LeetCode 304)、
class NumMatrix {
private:
    vector<vector<int>> prefix;
public:
    NumMatrix(vector<vector<int>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        prefix = vector<vector<int>>(m+1, vector<int>(n+1, 0));
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                prefix[i+1][j+1] = matrix[i][j] + prefix[i][j+1] 
                                  + prefix[i+1][j] - prefix[i][j];
            }
        }
    }
    
    int sumRegion(int row1, int col1, int row2, int col2) {
        return prefix[row2+1][col2+1] - prefix[row1][col2+1] 
             - prefix[row2+1][col1] + prefix[row1][col1];
    }
};

三、差分数组核心思想

差分数组(Difference Array) 是一种优化区间批量更新的技术,核心特性:

  1. 差分定义diff[i] = arr[i] - arr[i-1]diff[0] = arr[0]

  2. 区间更新:对区间[l, r]增加val → diff[l] += valdiff[r+1] -= val

  3. 还原数组:通过前缀和还原更新后的数组

适用场景:

  • 频繁区间增减操作

  • 航班预订统计、任务调度

  • 结合前缀和实现高效更新与查询


四、差分数组模板

区间增减与还原
class Difference {
private:
    vector<int> diff;
public:
    Difference(vector<int>& nums) {
        diff.resize(nums.size());
        diff[0] = nums[0];
        for (int i = 1; i < nums.size(); ++i) {
            diff[i] = nums[i] - nums[i-1];
        }
    }
    
    void increment(int l, int r, int val) {
        diff[l] += val;
        if (r + 1 < diff.size()) {
            diff[r+1] -= val;
        }
    }
    
    vector<int> getResult() {
        vector<int> res(diff.size());
        res[0] = diff[0];
        for (int i = 1; i < diff.size(); ++i) {
            res[i] = res[i-1] + diff[i];
        }
        return res;
    }
};

五、高频应用场景

场景1:和为K的子数组(LeetCode 560)
int subarraySum(vector<int>& nums, int k) {
    unordered_map<int, int> sumCount;
    sumCount[0] = 1; // 处理前缀和恰好为k的情况
    int prefix = 0, count = 0;
    
    for (int num : nums) {
        prefix += num;
        if (sumCount.count(prefix - k)) {
            count += sumCount[prefix - k];
        }
        sumCount[prefix]++;
    }
    return count;
}
场景2:航班预订统计(LeetCode 1109)
vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
    vector<int> diff(n + 1, 0); // 差分数组
    for (auto& b : bookings) {
        int l = b[0]-1, r = b[1]-1, val = b[2];
        diff[l] += val;
        diff[r+1] -= val;
    }
    
    vector<int> res(n);
    res[0] = diff[0];
    for (int i = 1; i < n; ++i) {
        res[i] = res[i-1] + diff[i];
    }
    return res;
}
场景3:二维区域和检索(LeetCode 308 扩展)
// 动态更新 + 二维前缀和
class NumMatrix {
private:
    vector<vector<int>> matrix;
    vector<vector<int>> prefix;
public:
    NumMatrix(vector<vector<int>>& mat) {
        matrix = mat;
        int m = matrix.size(), n = matrix[0].size();
        prefix = vector<vector<int>>(m+1, vector<int>(n+1, 0));
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                prefix[i+1][j+1] = matrix[i][j] + prefix[i][j+1] 
                                  + prefix[i+1][j] - prefix[i][j];
            }
        }
    }
    
    void update(int row, int col, int val) {
        int delta = val - matrix[row][col];
        matrix[row][col] = val;
        for (int i = row+1; i < prefix.size(); ++i) {
            for (int j = col+1; j < prefix[0].size(); ++j) {
                prefix[i][j] += delta;
            }
        }
    }
    
    int sumRegion(int row1, int col1, int row2, int col2) {
        return prefix[row2+1][col2+1] - prefix[row1][col2+1] 
             - prefix[row2+1][col1] + prefix[row1][col1];
    }
};

六、大厂真题实战

真题1:区间加法(某大厂2023面试)

题目描述:
初始化一个全零数组,执行多个区间加法操作后返回最终数组
差分数组解法:

vector<int> getModifiedArray(int length, vector<vector<int>>& operations) {
    vector<int> diff(length + 1, 0);
    for (auto& op : operations) {
        int l = op[0], r = op[1], val = op[2];
        diff[l] += val;
        if (r + 1 < length) diff[r+1] -= val;
    }
    
    vector<int> res(length);
    res[0] = diff[0];
    for (int i = 1; i < length; ++i) {
        res[i] = res[i-1] + diff[i];
    }
    return res;
}
真题2:连续子数组和(LeetCode 523)

题目描述:
判断是否存在长度≥2的子数组,其和是k的倍数
前缀和+哈希表解法:

bool checkSubarraySum(vector<int>& nums, int k) {
    unordered_map<int, int> remainderMap;
    remainderMap[0] = -1; // 处理前缀和本身满足条件的情况
    int prefix = 0;
    
    for (int i = 0; i < nums.size(); ++i) {
        prefix += nums[i];
        int rem = prefix % k;
        if (remainderMap.count(rem)) {
            if (i - remainderMap[rem] >= 2) return true;
        } else {
            remainderMap[rem] = i;
        }
    }
    return false;
}

七、常见误区与优化技巧

  1. 索引偏移错误:前缀和数组长度应为n+1,注意原数组与前缀和的索引对应关系

  2. 哈希表遗漏初始状态:未初始化sumCount[0] = 1导致漏解

  3. 差分数组越界:区间右端点r+1可能超出数组范围

  4. 优化技巧

    • 二维前缀和预处理公式的推导

    • 动态更新时的增量优化

    • 结合位运算压缩状态


LeetCode真题训练:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值