算法笔记(五)——小而美的算法技巧—前缀和

简介: 算法笔记(五)——小而美的算法技巧—前缀和

一维数组中的前缀和


先看一道例题,力扣第303题。

区域和检索——数组不可变


没学过前缀和之前我们都会这样写。

class NumArray {
private:
    vector<int> nums;
public:
    NumArray(vector<int>& nums) {
        this->nums=nums;
    }
    int sumRange(int left, int right) {
        int res=0;
        for(int i=left;i<=right;i++)
        {
            res+=nums[i];
        }
        return res;
    }
};

这样写没问题,但是效率很低,sumRange会被频繁的调用,时间复杂度为O(n)。

而这道题如果利用前缀和来解题,时间复杂度就会降到O(1).

简单先介绍下前缀和:核心思想就是,创建一个新数组sum出来,用sum[i]记录num[0,1,,,,i-1]的和。

image.png

如果我们想求索引区间[1,3]内所有元素的和,S[4]-s[1]就可以得出来,这样只需要做一次减法就可以得出来结果避免了多次for循环的调用,时间复杂度为O(1).

优化后的代码:

class NumArray {
public:
    vector<int> sum;
    NumArray(vector<int>& nums) {
        int n=nums.size();
        sum.resize(n+1);
      // 计算sum的累加和
        for(int i=0;i<n;i++)
        {
            sum[i+1]=sum[i]+nums[i];
        }
    }
     /* 查询闭区间 [left, right] 的累加和 */
    int sumRange(int left, int right) {
      return sum[right+1]-sum[left];
    }
};

趁热打铁再试一题:

image.png

image.png

#include<iostream>
using namespace std;
const int N=100010;
int n,m;
int a[N],sum[N];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    for(int i=0;i<n;i++) sum[i+1]=sum[i]+a[i];
    while(m--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",sum[r]-sum[l-1]);
    }
    return 0;
}

前面我有一篇关于蓝桥杯的博客,里面的K倍区间就是对前缀和的应用,不过那道题对前缀和做了优化,想深入了解的可以看下:蓝桥杯——2017第八届C/C++真题[省赛][B组]_skeet follower的博客-CSDN博客

二维矩阵中的前缀和


首先介绍下二维矩阵的前缀和,这篇文章写的很好前缀和,这里我就拿大佬的思路来供我们参考学习.

步骤一:求 preSum

我们先从如何求出二维空间的 preSum[i][j]。


我们定义 preSum[i][j]preSum[i][j] 表示 从 [0,0][0,0] 位置到 [i,j][i,j] 位置的子矩形所有元素之和。

可以用下图帮助理解:


S(O, D) = S(O, C) + S(O, B) - S(O, A) + D

3df5865ad0d49b5ffdb91bde6474c8cb.png

减去 S(O, A)S(O,A) 的原因是 S(O, C)S(O,C) 和 S(O, B)S(O,B) 中都有 S(O, A)S(O,A),即加了两次 S(O, A)S(O,A),所以需要减去一次 S(O, A)S(O,A)。


如果求 preSum[i][j]preSum[i][j] 表示的话,对应了以下的递推公式:

preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] - preSum[i - 1][j - 1] + matrix[i][j]

步骤二:根据 preSum 求子矩形面积

前面已经求出了数组中从 [0,0][0,0] 位置到 [i,j][i,j] 位置的 preSum。下面要利用 preSum[i][j]preSum[i][j] 来快速求出任意子矩形的面积。

同样利用一张图来说明:

S(A, D) = S(O, D) - S(O, E) - S(O, F) + S(O, G)

3771da52173f1edf8d39a1d83f4154d9.png

加上子矩形 S(O, G)S(O,G) 面积的原因是 S(O, E)S(O,E) 和 S(O, F)S(O,F) 中都有 S(O, G)S(O,G),即减了两次 S(O, G)S(O,G),所以需要加上一次 S(O, G)S(O,G)。

如果要求 [row1, col1][row1,col1] 到 [row2, col2][row2,col2] 的子矩形的面积的话,用 preSum 对应了以下的递推公式:

preSum[row2][col2] - preSum[row2][col1 - 1] - preSum[row1 - 1][col2] + preSum[row1 - 1][col1 - 1]

二维区域和检索——矩阵不可变


3f33d66b289d4cdc8d0e7a30057906f1.png

image.png

代码

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

image.png

image.png

代码

#include<iostream>
using namespace std;
const int N=1010;
int n,m,q;
int a[N][N],s[N][N];
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            scanf("%d",&a[i][j]);
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
        }
    }
    while(q--)
    {
        int x1,y1,x2,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        printf("%d\n",s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]);
    }
    return 0;
}
相关文章
|
3月前
|
算法
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
81 1
|
3月前
|
算法 索引
❤️算法笔记❤️-(每日一刷-141、环形链表)
❤️算法笔记❤️-(每日一刷-141、环形链表)
56 0
|
3月前
|
算法
【❤️算法笔记❤️】-(每日一刷-876、单链表的中点)
【❤️算法笔记❤️】-(每日一刷-876、单链表的中点)
56 0
|
3月前
|
算法
【❤️算法笔记❤️】-每日一刷-23、合并 K 个升序链表
【❤️算法笔记❤️】-每日一刷-23、合并 K 个升序链表
36 0
|
11天前
|
算法
|
3月前
|
算法 API 计算机视觉
人脸识别笔记(一):通过yuface调包(参数量54K更快更小更准的算法) 来实现人脸识别
本文介绍了YuNet系列人脸检测算法的优化和使用,包括YuNet-s和YuNet-n,以及通过yuface库和onnx在不同场景下实现人脸检测的方法。
96 1
|
3月前
|
JSON 算法 数据可视化
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
这篇文章是关于如何通过算法接口返回的目标检测结果来计算性能指标的笔记。它涵盖了任务描述、指标分析(包括TP、FP、FN、TN、精准率和召回率),接口处理,数据集处理,以及如何使用实用工具进行文件操作和数据可视化。文章还提供了一些Python代码示例,用于处理图像文件、转换数据格式以及计算目标检测的性能指标。
84 0
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
|
3月前
|
算法
❤️算法笔记❤️-(每日一刷-160、相交链表)
❤️算法笔记❤️-(每日一刷-160、相交链表)
22 1
|
3月前
|
数据可视化 搜索推荐 Python
Leecode 刷题笔记之可视化六大排序算法:冒泡、快速、归并、插入、选择、桶排序
这篇文章是关于LeetCode刷题笔记,主要介绍了六大排序算法(冒泡、快速、归并、插入、选择、桶排序)的Python实现及其可视化过程。
25 0
|
3月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
36 0

热门文章

最新文章