专题四:前缀和

@bit::Shadow
✧(≖ ◡ ≖✿

目录

1.前缀和(easy)

[第一步:预处理出一个前缀和数组 dp ](#第一步:预处理出一个前缀和数组 dp[ ])

[第二步:使用前缀和数组dp ](#第二步:使用前缀和数组dp[ ])

2.二维前缀和(easy)

第一步:构造二维前缀和数组

第二步:使用前缀和数组

3.寻找数组中心下标(easy)

[4. 除了自身以外数组的乘积(normal)](#4. 除了自身以外数组的乘积(normal))

5.找和为K的子数组(normal)

6.和为n的被K整除的子数组(normal)

7.连续最长和为0子数组(normal)

8.矩阵区域和(normal)


性质:利用空间换时间的思想,通过预处理+额外处理,快速得出数组中某连续区间值。

第一步:预处理一维/多维的前缀和/后缀和/乘积数组。

第二步:使用数组。

二维数组(x1,y1)~(x2,y2)区域面积和公式:

S = dpx2y2+dpx1-1y1-1 - dpx1-1y2 - dpx2y1-1;

图解:

1.前缀和(easy)

对于给定的长度为n的数组{a1,a2,··,an},我们有m次查询操作,每一次操作给出两个参

数l,r,你需要输出数组中第l到第r个元素之和。

输出描述:

对于每一次查询操作,在一行上输出一个整数,代表区间和。

示例1

输入:

3 2

1 2 4

1 2

2 3

输出:3

第一步:预处理出一个前缀和数组 dp

计算原理

第二步:使用前缀和数组dp

对应输出

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

int main() {
    int n,m;//n元素数量 m次查询
    cin >> n >> m;
    long long* dp = new long long[n+1]();//初始化为0
    for(int i = 1;i <= n;i++)
    {
        cin >> dp[i];
        if(i != 1)
            dp[i] += dp[i - 1]; 
        // cout << dp[i] << " ";
    }
    // cout << endl;
    int l,r;
    for(int i = 0;i < m;i++)
    {
        cin >> l >> r;
        cout << dp[r] - dp[l-1] << endl;
    }
}

2.二维前缀和(easy)

基于原数组arr的二维推导公式:

第一步:构造二维前缀和数组

cpp 复制代码
dp[i][j] = dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+nums[i-1][j-1];

第二步:使用前缀和数组

求arr两坐标(x1,y1)与(x2,y2)间面积公式:

S = dpx2y2+dpx1-1y1-1 - dpx1-1y2 - dpx2y1-1;

注意D区域被减去两次。

永远:

x2 > x1

y2 > y1

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

int main() {
    int line,col,q;
    cin >> line >> col >> q;

    // vector<vector<int>> dp((line+1)*(col+1),0);
    vector<vector<long long>> dp(line+1,vector<long long>(col+1, 0));
                                                    //真正的内部分配0
    // long long* dp = new long long[(line+1)*(col+1)]();//语法正确但是访问不能以[i][j]式访问
    // long long* dp = new long long[line+1][col+1]();//语法错误

    for(int i = 1;i <= line;i++)
    {
        for(int j = 1;j <= col;j++)
        {
            cin >> dp[i][j];
            dp[i][j] += dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1];
            // cout << dp[i][j] << " ";
        }
        // cout << endl;
    }
    //[i][j]处表示自1,1到i,j的和值
    for(int i =0;i < q;i++)
    {
        int x1,y1;
        int x2,y2;
        cin >> x1 >> y1 >> x2 >> y2;
        cout << dp[x1-1][y1-1]+dp[x2][y2]-dp[x1-1][y2]-dp[x2][y1-1] << endl;
    }
}
// 64 位输出请用 printf("%lld")

3.寻找数组中心下标(easy)

思路:在原dp数组两侧添加'0'标记位作为判断结束返回"-1"的依据。

给你一个整数数组 nums ,请计算数组的 中心下标

数组中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。

如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。

如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1

复制代码
输入:nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。
cpp 复制代码
class Solution {
public:
    int pivotIndex(vector<int>& nums) 
    {
        int len = nums.size();

        vector<int> dp(len+2,0);//len+2个

        for(int i = 1;i <= len;i++)
        {
            dp[i] =dp[i-1] + nums[i-1];
        }
        for(int c = 1;c <= len;c++)
        {
            if(dp[c-1] == dp[len]-dp[c]) return c-1;
        }
        return -1;
    }
};

4.除了自身以外数组的乘积(normal)

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除了 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

不要使用除法, 且在 O(n) 时间复杂度内完成此题。

复制代码
输入: nums = [1,2,3,4]输出: [24,12,8,6]
cpp 复制代码
class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        vector<int> fdp(n+1,1);
        vector<int> bdp(n+1,1);
        vector<int> ret(n,1);
        for(int i = 1;i <= n;i++)
        {
            fdp[i] = fdp[i-1]*nums[i-1];
            bdp[i] = bdp[i-1]*nums[n-i];
            // cout << fdp[i] << " " << bdp[i] << endl;
        }

        for(int i =0;i<n;i++)
        {
            // if(i == 0) 
            // {
            //     ret[i] = bdp[n-1];
            //     continue;
            // }
            //          对应错误!原因:fdp中前置1与第一个真值"1"搞混了草稿打错
            //                   再者:既然bdp都没有越界 fdp怎么会越界额外判断呢?
            // ret[i] = fdp[i-1]*bdp[n-i-1];
            ret[i] = fdp[i]*bdp[n-i-1];
        }
        return ret;
    }
};

5.找和为K的子数组(normal)

err

这里竟然使用哈希表来辅助解决负数出现的顺序问题

1.边统计边验证是否符合sum-k值

2.count+=sum-k//而不是++ 代表同时计入旧子数组数目

3.unordered_map<int, int>中find库函数的使用


给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k的子数组的个数

子数组是数组中元素的连续非空序列。

示例 1:

复制代码
输入:nums = [1,1,1], k = 2
输出:2

示例 2:

复制代码
输入:nums = [1,2,3], k = 3
输出:2

提示:

  • 1 <= nums.length <= 2 * 104
  • -1000 <= nums[i] <= 1000
  • -107 <= k <= 107
cpp 复制代码
class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> hash;
        hash[0]=1;
        int sum = 0, count=0;
        for(auto i:nums)
        {
            sum += i;
            if(hash.find(sum - k) != hash.end()) count+=hash[sum-k];
            hash[sum]++;
        }
        return count;
    }
};

6.和为n的被K整除的子数组(normal)

本题不依赖课纯手搓1.6h

前缀合:

思路一:手搓(难死了呜呜呜😭)

fdp结合哈希表<int, int> 词典

0.边纳入边+=补余数集。

1.查找+=首余数集。

2.二次+=副余数集。

cpp 复制代码
#include<stdio.h>
class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) {
        vector<int> fdp(nums.size()+1,0);
        // unordered_map<int, int> _hash;
        int count = 0;
        for(int i =1;i <= nums.size();i++)
        {
            fdp[i] = fdp[i-1] + nums[i-1];
            // cout << fdp[i] << " ";
        }
        unordered_map<int, int> hash;
        hash[0]++;
        for(int i = 1;i <= nums.size();i++)
        {
            hash[fdp[i]%k]++;
            count+=hash[fdp[i]%k]-1;//减1处理刚累加值
            // 取n%7的两个正负余数
            int t = fdp[i] % k > 0 ? fdp[i] % k - k : fdp[i] % k + k;
            if (hash[t] >= 1)
                count += hash[t];
        }

        return count;
    }
};

思路二:

优先解决"余数"问题:负%正==负 每个数(除0)都有两个数可以补充其无法整除的效果(像6%5=1 4、-1都可以补充)

同余定理:( a ± b) % k == 0 ---> a%k == b%k

正负余数统一化

cpp 复制代码
class Solution {
public:
    int subarraysDivByK(vector<int>& nums, int k) {
        unordered_map<int,int> hash;
        vector<int> v(nums.size()+1,0);
        hash[0]++;
        for(int i = 1;i <= nums.size();i++)
            v[i] = v[i-1]+nums[i-1];
        //
        int cnt = 0;
        for(int i = 1;i <= nums.size();i++)
        {
            hash[(v[i]%k+k)%k]++;//负数修正--统一化
            
            if(hash[(v[i]%k+k)%k] != 1)
                cnt+=hash[(v[i]%k+k)%k]-1;
        }
        return cnt;
    }

};

反思:

你应该先想到余数问题的两种情况!!

然后才能优化逻辑。

怎么想到余数问题的两种情况?解决这类问题正是"技术能力""优化能力"的关键特别是对于我的学习领域。

广泛适当跳脱的思考,数字的敏感。

7.连续最长和为0子数组(normal)

err但是:思路+手动实现

给定一维数组,元素为1或0。找到含有相同数量01最长连续子数组,并返回该子数组的长度

1.问题转换:凭借数字的敏感,与数据复杂度。将0记作-1,1记作1。那么题意就是要求求最长的和为0的子数组。

2.前缀和+哈希表。 前缀和求和,哈希表存储键值。

哈希表 < 前缀和, 下标 >

3.什么时候将键值对存储在哈希表内?

使用完后。也可以使用前不过要报对应下标提前存储。

cpp 复制代码
class Solution {
public:
    int findMaxLength(vector<int>& nums) {
        vector<int> dp(nums.size()+1,0);
        for(int i = 1;i <= nums.size();i++)
        {
            if(nums[i-1] == 0)
                dp[i] = -1+dp[i-1];
            else
                dp[i] = nums[i-1]+dp[i-1];
            printf("dp[%d]:%d \n",i,dp[i]);
        }
        int ret = 0;
        unordered_map<int,int> hash;
        vector<int> v(nums.size()+1,0);//存储长度v[5]=5-1
        hash[0] = 0;
        for(int i = 1;i <= nums.size();i++)
        {
            //***************************************
            //先使用:即查找目标值
            if(hash.find(dp[i]) != hash.end())
            {
//           ***长度存储***
                v[i] = i - hash[dp[i]];

                printf("v[%d]:%d ",i,v[i]);
                ret = v[i] > ret ? v[i]:ret;
            }
            //***************************************

            //使用完后纳入哈希表 <前缀和,下标>
            //if(hash[dp[i]] == 0)//err与hash[0]矛盾
            if(hash.find(dp[i]) == hash.end())//若dp[i]先前已经存储了,重复不纳入
                hash[dp[i]] = i;
        }
        return ret;
    }
};

哈希表存储什么?// err

关键在存储什么吗?关键在于怎样建立映射关系 其中包含对应下标出其长度值其遍历前位置

8.矩阵区域和(normal)

非常经典的二维区域定值处理中等题。

一丁丁点提示+手搓。

解题过程概述:逻辑抽象(35)+代码实现(15)+调试(50)。

难点:

元素块溢出 处理

待剪块定位处理

_r与_c的映射错误而且是两次错误,原因:

1.逻辑性错误,以c来决定_c边界。

2.两张表下标照应处理混乱。


给你一个 m x n 的矩阵 mat 和一个整数 k ,请你返回一个矩阵 answer ,其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和:

  • i - k <= r <= i + k,
  • j - k <= c <= j + k
  • (r, c) 在矩阵内。

例如

题意理解抽象以行列双循环,自0,0遍历所有元素每个元素作为正方形(边长: 2*k+1)的中心点。

超出则截断,仅保留有效元素。

求各个元素对应的方形内所有元素的和值,并将此和值赋到返回的二维数组的对应位置。

例:k==1

mat22处,对应方形如图青色元素值,全都交给返回对象的ret22处。

因此我们要使用二维前缀和的方法来解决此问题:

二维和值索引需上左额外加一(000)层,以方便求值。

当k=1方形区边长为3,以1,1为例:

代码实现

cpp 复制代码
class Solution {
public:
    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
//      **********            
        //逻辑原理--->代码实现 而非 数值规律--->代码实现
//      **********
        //二维矩阵和
        vector<vector<int>> vv(mat.size()+1, vector(mat[0].size()+1, 0));
        for(int i = 1;i <= mat.size();i++)
        {
            for(int j = 1;j <= mat[0].size();j++)
            {
                vv[i][j] = vv[i-1][j]+vv[i][j-1]-vv[i-1][j-1]+mat[i-1][j-1];
                printf("%d  ",vv[i][j]);
            }
        }
        //
        int row = vv.size();//行数5
        int col = vv[0].size();//列数4
        vector<vector<int>> ret(row-1,vector<int>(col-1,0));
        for(int i = 0;i < row-1;i++)//row-1减去多加的一行 循环次数由真矩阵行决定
        {
            int r = k+1+i > row-1? row-1 : k+1+i;//溢出保留最大行
            //_r定位减去值
            int _r = i > k? i-k:0;
            for(int j = 0;j < col-1;j++)//row-1减去多加的一列 循环次数由真矩阵列决定
            {
                int c = k+1+j > col-1? col-1 : k+1+j;//溢出保留最大列
                int _c = j > k? j-k:0;
                // printf("%d%d\n",r,c);//重要⚠️验证行列的定位
                // printf("%d%d  %d %d %d %d  ",r,c,vv[r][c],vv[_r][_c],vv[r][_c],vv[_r][c]);
                ret[i][j] = vv[r][c]+vv[_r][_c]-vv[r][_c]-vv[_r][c];
            }
        }
        return ret;
    }
};

感谢支持,持续更新

欢迎关注

相关推荐
JAVA面经实录9171 小时前
高频算法面试题
java·计算机网络·算法·面试
qq_452396231 小时前
第十一篇:《资源管理:Requests/Limits、ResourceQuota、LimitRange》
算法·贪心算法
Tisfy1 小时前
LeetCode 2095.删除链表的中间节点:两次遍历 / 一次遍历(快慢指针)
算法·leetcode·链表·题解·双指针
Irissgwe1 小时前
AVL树详解
数据结构·c++·算法·二叉树·c·二叉搜索树·avl
凌波粒2 小时前
LeetCode--131.分割回文串(回溯算法)
算法·leetcode·职场和发展
北域码匠2 小时前
奇偶归并排序:并行计算的排序利器
数据结构·算法·c#·排序算法
成都易yisdong2 小时前
上海某平面坐标系与CGCS2000坐标互转详解(含全域拟合点、实战案例、保密规范)
大数据·人工智能·算法
玖玥拾2 小时前
C/C++ 数据结构(五)链表的应用、对象池
c语言·数据结构·c++·链表·对象池·双向链表
2601_961845152 小时前
花生十三网课网盘|百度网盘|下载
数据结构·算法·链表·贪心算法·排序算法·线性回归·动态规划