【杂乱算法】前缀和与差分

前缀和

一维

一个数组prefix中,第i个元素表示nums[0]nums[i-1]的总和,那么我们就称这个prefix数组是nums数组的前缀和。
prefix [ i ] = ∑ j = 0 i nums [ j ] \text{prefix}[i] = \sum_{j=0}^{i} \text{nums}[j] prefix[i]=j=0inums[j]

应用

1、快速计算下标为[i , j]区间的和。

  • prefix[j+1]- prefix[i]即为下标[i , j]之间元素的总和。

二维

matrix-sum.png

class NumMatrix {
    vector<vector<int>> sum;
public:
    NumMatrix(vector<vector<int>> &matrix) {
        int m = matrix.size(), n = matrix[0].size();
        sum.resize(m + 1, vector<int>(n + 1));
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] - sum[i][j] + matrix[i][j];
            }
        }
    }

    // 返回左上角在 (r1,c1) 右下角在 (r2,c2) 的子矩阵元素和
    int sumRegion(int r1, int c1, int r2, int c2) {
        return sum[r2 + 1][c2 + 1] - sum[r2 + 1][c1] - sum[r1][c2 + 1] + sum[r1][c1];
    }
};

作者:灵茶山艾府
链接:https://leetcode.cn/problems/range-sum-query-2d-immutable/solutions/2667331/tu-jie-yi-zhang-tu-miao-dong-er-wei-qian-84qp/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

差分

一维

所谓“差分”,是指原数组中每个元素与前一元素之差所形成的数组
我们知道,对原数组进行诸位累加(前缀计算操作),所得到的数组为前缀和数组。差分数组,则是对其执行前缀计算后,能够得到原数组的那个数组 。

差分数组的主要作用,是帮助快速修改某段区间。

因此,当我们想要对原数组的 [l,r] 进行整体修改时,只需要对差分数组的lr+1 位置执行相应操作即可。

二维

扩展

1、前缀和与哈希表

力扣560.和为k的子数组
借助哈希表中判定重复元素的功能,可以帮忙判断(当前的前缀和-K)是否出现在哈希表中,如果有那么久数量加一,如果没有就将当前前缀和压入哈希表。

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> haxi; // 用于存储前缀和出现次数
        haxi[0] = 1; // 初始化,表示前缀和为0出现一次
        vector<int> qian(nums.size() + 1, 0); // 前缀和数组
        int ans = 0;
        
        // 计算前缀和
        for (int i = 1; i <= nums.size(); i++) {
            qian[i] = nums[i - 1] + qian[i - 1];
        }

        // 查找满足条件的子数组
        for (int i = 1; i <= nums.size(); i++) {
            int complement = qian[i] - k;
            if (haxi.find(complement) != haxi.end()) {
                ans += haxi[complement]; // 增加满足条件的子数组个数
            }
            haxi[qian[i]]++; // 更新当前前缀和的出现次数
        }
        
        return ans;
    }
};

或者也可以一次遍历即可。在遍历的同时判断(当前的前缀和-K)是否出现在哈希表中。

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int ans = 0, s = 0;
        unordered_map<int, int> cnt{{0, 1}}; // s[0]=0 单独统计
        for (int x : nums) {
            s += x;
            // 注意不要直接 += cnt[s-k],如果 s-k 不存在,会插入 s-k
            ans += cnt.contains(s - k) ? cnt[s - k] : 0;
            cnt[s]++;
        }
        return ans;
    }
};

作者:灵茶山艾府
链接:https://leetcode.cn/problems/subarray-sum-equals-k/solutions/2781031/qian-zhui-he-ha-xi-biao-cong-liang-ci-bi-4mwr/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2、差分数组的逆运用(前缀和)

差分数组的定义是每个位置的值表示原数组在该位置的累积变化量。

具体来说,如果你有一个差分数组 diff,通过计算其前缀和得到的数组 prefix,那么 prefix[i] 表示从开始到位置 i 的所有增量或减量的总和(变化量的总和)。
力扣1109.航班预定统计

class Solution {
public:
    vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
        vector<int> temp(n+1, 0); 
        vector<int> ans(n, 0); 
        for(auto &x : bookings) {
            int first = x[0];
            int last = x[1];
            int seats = x[2];
            temp[first] += seats;
            if (last + 1 <= n) {	//注意这里的范围判断,最后一个元素的last+1溢出
                temp[last + 1] -= seats;
            }
        }
        int sum = 0;
        for(int i = 1; i <= n; i++) { 
            sum += temp[i];
            ans[i - 1] = sum; //加了多少个差分数组的和就是第多少个的航班的预定
        }
        return ans;
    }
};

力扣2381.字母移位2

class Solution {
public:
    string shiftingLetters(string s, vector<vector<int>>& shifts) {
        int n = s.length();
        vector<long long> shiftSum(n + 1, 0);
        
        // 计算每个位置的累计移位
        for (const auto& shift : shifts) {
            int start = shift[0], end = shift[1];
            int direction = shift[2] ? 1 : -1;
            shiftSum[start] += direction;
            shiftSum[end + 1] -= direction;
        }
        
        // 计算前缀和(变化量)
        for (int i = 1; i <= n; ++i) {
            shiftSum[i] += shiftSum[i-1];
        }
        
        // 应用移位到原字符串
        for (int i = 0; i < n; ++i) {
            int shift = ((shiftSum[i] % 26) + 26) % 26;  // 确保结果为正
            s[i] = ((s[i] - 'a' + shift) % 26) + 'a';
        }
        
        return s;
    }
};

3、前缀和与二分(路径和)

二分查找:将数组进行排序后判断nums[mid]target的大小。直到找到或者是遇到终点。
但是对于不同的闭开方式,终点有不同的说法:

  • 左闭右闭:终点为left<=right
  • 左闭右开/左开右闭:终点为left<right
  • 左开右开:终点为left+1<right
class Solution {
   // lower_bound 返回最小的满足 nums[i] >= target 的 i
   // 如果数组为空,或者所有数都 < target,则返回 nums.size()
   // 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]

   // 闭区间写法
   int lower_bound(vector<int> &nums, int target) {
       int left = 0, right = (int) nums.size() - 1; // 闭区间 [left, right]
       while (left <= right) { // 区间不为空
           // 循环不变量:
           // nums[left-1] < target
           // nums[right+1] >= target
           int mid = left + (right - left) / 2;
           if (nums[mid] < target) {
               left = mid + 1; // 范围缩小到 [mid+1, right]
           } else {
               right = mid - 1; // 范围缩小到 [left, mid-1]
           }
       }
       return left;
   }

   // 左闭右开区间写法
   int lower_bound2(vector<int> &nums, int target) {
       int left = 0, right = nums.size(); // 左闭右开区间 [left, right)
       while (left < right) { // 区间不为空
           // 循环不变量:
           // nums[left-1] < target
           // nums[right] >= target
           int mid = left + (right - left) / 2;
           if (nums[mid] < target) {
               left = mid + 1; // 范围缩小到 [mid+1, right)
           } else {
               right = mid; // 范围缩小到 [left, mid)
           }
       }
       return left; // 返回 left 还是 right 都行,因为循环结束后 left == right
   }

   // 开区间写法
   int lower_bound3(vector<int> &nums, int target) {
       int left = -1, right = nums.size(); // 开区间 (left, right)
       while (left + 1 < right) { // 区间不为空
           // 循环不变量:
           // nums[left] < target
           // nums[right] >= target
           int mid = left + (right - left) / 2;
           if (nums[mid] < target) {
               left = mid; // 范围缩小到 (mid, right)
           } else {
               right = mid; // 范围缩小到 (left, mid)
           }
           // 也可以这样写
           // (nums[mid] < target ? left : right) = mid;
       }
       return right;
   }

public:
   vector<int> searchRange(vector<int> &nums, int target) {
       int start = lower_bound(nums, target); // 使用其中一种写法即可
       if (start == nums.size() || nums[start] != target) {
           return {-1, -1}; // nums 中没有 target
       }
       // 如果 start 存在,那么 end 必定存在
       int end = lower_bound(nums, target + 1) - 1;
       return {start, end};
   }
};

作者:灵茶山艾府
链接:https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/solutions/1980196/er-fen-cha-zhao-zong-shi-xie-bu-dui-yi-g-t9l9/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

力扣2602

技巧:可以用面积之差来表示路径长度。
题解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值