[LC优选算法#15] 前缀和 | 一维前缀和 | 二维前缀和 | 寻找数组的中心下标

1. 前缀和思想

前缀和是指从数组第一个元素开始,累加到当前位置得到的累计和序列。是典型的空间换时间思想。

以一维前缀和数组为例:

2. 例题分析

2.1 一维前缀和

一维前缀和

解题思路:

假设一共查询Q次:

  1. 暴力+模拟 O(N * Q):每次查询时,计算出给定的区间和并返回。

由于查询次数可能会很大,暴力解法的时间复杂度会随着Q的增大而倍增。因此可以空间换时间 ,使用前缀和思想。

  1. 前缀和 时间O(N + Q),空间O(N):新建一个前缀和数组,每次查询时直接返回区间和 (两个前缀和之)。

遍历数组计算前缀和,需要O(N)的时间;查询Q次,每次计算消耗O(1)的时间。因此总的时间复杂度为O(N + Q),空间复杂度为O(N)

cpp 复制代码
#include<iostream>
#include<vector>
using namespace std;

int main()
{
    int n, t; cin >> n >> t;
    vector<long long> v(n + 1, 0);

    for(int i=1; i<=n; i++)
    {
        int x; cin >> x;
        v[i] = v[i - 1] + x;
    }

    while(t--)
    {
        int l, r; cin >> l >> r;
        cout << v[r] - v[l - 1] << endl;
    }

    return 0;
}

2.2 二维前缀和

二维前缀和

解题思路:

类比于一维前缀和数组的形式,我们可以试着处理从[0, 0]位置到[i, j]位置这片区域内所有元素的累加和,这样就可以在O(1)的时间内计算出矩阵内任意区域的元素累加和。

  1. 二维前缀和 时间O(N^2 + Q),空间O(N^2):读取数组元素,据此新创建一个二维前缀和数组,最后根据输入的坐标计算区域的累加和即可。

在创建二维前缀和数组时,为了方便写代码和避免越界访问,我们一般会把第一行第一列 全部设置为0,从dp[1][1]开始填入数据。对应关系是行列相差1

和一维前缀和相同的策略,我们也是采用一边遍历,一边计算前缀和的方式填数。下图演示了前缀和数组的填数和使用

cpp 复制代码
#include<iostream>
#include<vector>
using namespace std;

int main() {
    int n, m, q;
    cin >> n >> m >> q;
    vector<vector<long long>> vv(n + 1, vector<long long>(m + 1, 0));

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> vv[i][j];
        }
    }

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            vv[i][j] += vv[i - 1][j] + vv[i][j - 1] - vv[i - 1][j - 1];
        }
    }

    while (q--) {
        int x1, x2, y1, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        long long ret = vv[x2][y2] - vv[x1 - 1][y2] - vv[x2][y1 - 1] + vv[x1 - 1][y1 -
                        1];
        cout << ret << endl;
    }

    return 0;
}

2. 3 寻找数组的中心下标

寻找数组的中心下标

解题思路:

我们发现对于每一个下标都要计算其左侧和右侧的区间和,因此采用前缀和的解法优化时间复杂度:

  1. 前缀和 时间O(N),空间O(N):预处理出一个前缀和数组和一个后缀和数组,遍历下标,从这两个数组中找出左右区间和相等的下标,若没有则返回-1。

注意点

  • 前缀和数组fx中,fx[i]表示[0, i - 1]区间所有元素的和
  • 后缀和数组gx中,gx[i]表示[i + 1, n - 1]区间所有元素的和
cpp 复制代码
class Solution {
public:
    int pivotIndex(vector<int>& nums)
    {
        int n = nums.size();
        vector<int> fx(n, 0);
        vector<int> gx(n, 0);

        fx[0] = nums[0];
        gx[n - 1] = nums[n - 1];

        for(int i=1; i<n; i++)
        {
            fx[i] = fx[i - 1] + nums[i];
        }

        for(int i=n-2; i>=0; i--)
        {
            gx[i] = gx[i + 1] + nums[i];
        }

        for(int i=0; i<n; i++)
        {
            int left = (i == 0 ? 0 : fx[i - 1]);
            int right = (i == n - 1 ? 0 : gx[i + 1]);

            if(left == right)
            {
                return i;
            }
        }

        return -1;
    }
};

// 本期内容就到这里啦,如果对你有帮助,请三连支持!我是青云,我们下期见^_~