当前位置: 首页 > news >正文

leetcode前缀和(C++)

目录

1.一维前缀和模板

2.二维前缀和模板

3.寻找数组的中心下标

4.除自身以外数组的乘积

5.和为k的子数组

6.和可被k整除的子数组

7.连续数组

8.矩阵区域和


1.一维前缀和模板

牛客网【模板】前缀和

给出 n 个元素的数组,进行 m 次操作,每次操作输出由 初始下标 到 截止下标 的所有元素相和结果

1.暴力解法:

如上图所示,问哪段区间,就对哪段区间进行一次循环相和

最坏情况每次都要整个数组相和,因此时间复杂度为O(m*n)


2.前缀和 -> 快速求出数组中某一个连续区间的和

前缀和是一个简化版的动态规划,dp[i] 表示 [1,i] 区间内所有元素的和

而dp数组中的 i 下标元素也不要傻傻的,对原数组的 [1,i] 进行求和得出;通过下图可以看出:dp[i] = dp[i-1] + arr[i] 

  

3.使用前缀和数组

当我们求[L,r]区间的值相和,那么就相当于求整个一段 [0,r] - [0,L-1] 的结果,此处必须是减去 [0,L-1] ,因为我们求的是闭区间的 [L,r] 值相和;换到 dp 数组的话,就相当于 dp[r] - dp[L-1]

4.为什么前缀和题目的下标要从1开始?

假设我们要求 [0,r] 区间的前缀和,如果下标从0开始算,那么就会出现 dp[r] - dp[-1] ,数组越界出大问题了,因此必须要从1开始

5.需要注意 10^9 int类型 无法存下,需要换成 long 类型

代码:

#include<iostream>
#include<vector>
using namespace std;int main()
{int n,m;cin>>n>>m;vector<long> arr(n+1,0);for(int i=1;i<=n;i++) cin>>arr[i];vector<long> dp(n+1,0);for(int i=1;i<=n;i++) dp[i] = dp[i-1] + arr[i];for(int i=1;i<=m;i++){int left,right;cin>>left>>right;cout<<(dp[right] - dp[left-1])<<endl;}return 0;
}

2.二维前缀和模板

牛客网【模板】二维前缀和

给出 n 行 m 列 的二维数组,进行 q 次操作,每次返回 (x1,x2) -> (y1,y2) 的矩阵区间的所有元素和,如下图所示:求出 (1,1) -> (2,2) 的区间和,最后结果为 1 + 2 + 3 + 2 = 8

如同一维数组前缀和,二维数组前缀和也需要设计出一个dp表

dp[i][j]:从 [1,1] 位置到 [i,j] 位置,该段区间内所有元素的和


如下图所示,我们将 dp[i][j] 区间内划分为4个部分,dp[i][j] = A + B + C + D

D和A都比较好表示,分别是 arr[i][j] 和 dp[i-1][j-1],但是B、C无法用已有的内容进行表示,所以我们可以转换一下表达式,改为 dp[i][j] = (A + B) + (A + C) + D - A

A+B区间和即为dp[i-1][j]         A+C区间和即为dp[i][j-1]

所以 dp[i][j] = dp[i-1][j] + dp[i][j-1] + arr[i][j] - dp[i-1][j-1]

前缀和数组填完以后,开始使用前缀和数组

如下图所示,假设我们要求 [x1,y1] ~ [x2,y2] 的区间和,那么相当于 D = dp[x2][y2] - A - B - C

B、C不好表示,换成 D = dp[x2][y2] - (A + B) - (A + C) + A

所以 D = dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1]  

代码:

#include<iostream>
#include<vector>
using namespace std;int main()
{int n,m,q;cin>>n>>m>>q;vector<vector<long>> arr(n+1,vector<long>(m+1,0));for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>arr[i][j];//求出前缀和数组 vector<vector<long>> dp(n+1,vector<long>(m+1,0));for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)dp[i][j] = dp[i-1][j] + dp[i][j-1] + arr[i][j] - dp[i-1][j-1];//使用前缀和数组for(int i=1;i<=q;i++) {int x1,x2,y1,y2;cin>>x1>>y1>>x2>>y2;cout<<( dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1] )<<endl;}return 0;
}

3.寻找数组的中心下标

中心下标:数组的一个下标,其左侧所有元素相和等于右侧所有元素相和,中心下标位置处的元素忽略

寻找到数组的中心下标,如果有多个则返回最左边的那个

使用一维前缀和模板求出前缀和数组,然后使用

如下图1所示,判断中心坐标即判断 [0,i-1] 与 [i+1,n-1] 区间和是否相等,如果相等即返回,不存在最后就返回-1

[0,i-1]区间和:dp[i-1]

[i+1,n-1]区间和:dp[n-1] - dp[i]

因为需要dp下标从1开始计算,所以可以先按照下标0开始计算的情况,完成所有代码以后对所有dp下标进行+1操作(下标从0开始算如下图2所示)

 图1

图2

代码:

class Solution {
public:int pivotIndex(vector<int>& nums) {int n = nums.size();vector<int> dp(n+1,0);for(int i=1;i<=n;i++) dp[i] = dp[i-1] + nums[i-1];//极端情况,情况3if(!(dp[n]-nums[0])) return 0;//使用前缀和数组for(int i=1;i<=n-1;i++)if(dp[i] == (dp[n] - dp[i+1])) return i;//极端情况,情况2return -1;}
};

4.除自身以外数组的乘积

返回数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 

1.前缀和:使用前缀和数组时,无法使用单一数组来进行表示;首先因为单一数组表示会使用到除法,来将多余的乘值去除;同时只要出现1次0,接下来前缀和数组中所有的值都会为0;正因此,我们使用两个前缀和数组( f[i] and g[i] )来分别表示 i 下标位置前的前缀和与i 下标位置后的前缀和


2.具体前缀和数组表示与细节:

  • f[i] 表示 [0,i-1] 区间内的前缀和,f[i] = f[i-1] * nums[i]
  • g[i] 表示 [i+1,n-1] 区间内的前缀和,顺序遍历求出前缀和不好,因为下标得从 i + 2 开始起算,容易做着做着做迷糊了;因此我们可以从后往前遍历,即 g[i] = g[i+1] * nums[i]
  • 当一个前缀和数组被填满时,因有 nums.size() - 1 个数据,所以每一个前缀和数组应该为nums.size()的大小,即 [ 1,nums.size() ];但在前缀和数组初始化时开nums.size()+1的空间才会比较方便,把不需要的 f[n] g[0]也给直接算进来,同时也不需要担心两者的边界情况;也正因此,后半部分符合条件,但前半部分还需进行微调;以 f[1] 为例,f[1] = f[0] * nums[1] ,所以nums[i]需要改成nums[i-1]
  • 最后的结果即为 f[i] * g[i+1] ,因为 f[i] 是有nums[i-1]相乘所得到的,所以最后统计结果时需要+1;g[i] 是从后往前遍历得到,因此最终的前缀和值在 g[i+1] 中
  • 初始化时,两个前缀和数组默认值都应该为1,才不会影响结果

  

代码:

class Solution {
public:vector<int> productExceptSelf(vector<int>& nums) {int n = nums.size();vector<int> f(n+1,1);vector<int> g(n+1,1);vector<int> ret;//创建前缀和数组for(int left=1;left<=n;left++)   f[left] = f[left-1] * nums[left-1];for(int right=n-1;right>=0;right--) g[right] = g[right+1] * nums[right];//统计结果for(int i=0;i<=n-1;i++)ret.push_back(f[i]*g[i+1]);return ret;}
};

5.和为k的子数组

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

 

1.暴力解法:把所有的子数组统计一遍,得出有几个子数组满足结果

2.滑动窗口是否可以解决该问题?

答:不可以解决,因为数组中会有0、负数和正数3种类型的数值,因此right、left无法一直往右移动


3.前缀和结合哈希表:

dp[i] : 到 i 位置的所有元素和

不难发现,如果我们要求出值为k的子数组,数组的left、right两个指针都得移动;因此我们采取正难则反的思想,即求出 [0,i] 的区间的总和 sum,再求出该区间 sum - k 有多少个,如下图所示;sum - k 的left指针固定,因为不固定的话,最后得出的 sum - k 可能并非一个连续数组(子数组),完美贴合了前缀和数组dp[i]的特性

细节问题:

  • 如何使用前缀和数组?若对 [0,i] 区间遍历一遍,找出该区间内dp数组有几个元素为 sum - k 的,时间复杂度为 O(N^2) + O(N),还不如暴力解法;因此我们需要搞一个哈希表,键为前缀和结果,值为前缀和结果出现次数;每次i向后移动就会有 sum 不同,从而导致了 sum - k 不同,需要的前缀和结果也就不同 
  • 在计算 i 位置前,哈希表里保存 [0,i-1] 位置的前缀和
  • 通过这个哈希表的前缀和结果存储,dp数组的存在就变得画蛇添足了;dp[i] = dp[i-1] +nums[i] 以后,我们无需知道 dp[i-2]、dp[i-3] …… 的前缀和是什么,并且也在哈希表中有统计,因此可以通过一个变量sum来解决 
  • 当整个数组结果为k时(如下图所示),我们并不会有一个 [0,-1] 区间去统计结果,因此hash初始化时要考虑到该情况,hash[0] = 1 and sum - k = 0

代码:

class Solution {
public:int subarraySum(vector<int>& nums, int k) {int ret = 0,sum = 0;unordered_map<int,int> hash;hash[0] = 1;for(auto x:nums){sum += x ; //dp[i] = dp[i-1] + nums[i]//统计[0,i-1]的结果if(hash.count(sum-k)) ret += hash[sum-k];//更新哈希表hash[sum]++;}return ret;}
};

6.和可被k整除的子数组

返回和为 n*k(n = 1,2,……)的子数组

1.同余定理:( a - b ) ÷ p = 0 (两数之差能够整除p)-> a % p = b % p

证明:

以 7 % 2 = 1 为例,就相当于 2的倍数 + 余数,因此可以写成 (1+2*3)% 2 = 1,所以 (a + p*k)% p = a % p

(a - b)÷ p = k 时,a - b = p * k -> a = b + p * k -> a % p = (b + p * k) % p = b % p


2.c++中,[负数%正数] 的结果为:负数 % 正数 == 负数(-7 % 2 == -1)

修正为 a % p + p ,将结果从负数转化为正数;但此时结果又出了问题,a % p + p ≠ a % p,所以还得再模上一个p,即 (a % p + p)% p = a % p


3.如下图所示,要求出和可以被k整除的子数组,即 (sum-x) % k = 0

根据同余定理,sum % k = x % k ,因此题目可以修改为在 [0,i-1] 区间内找出有多少个前缀和 x % k -> (x % k + k) % k 的结果为 (sum % k + k) % k,前缀和 x 也可以用 sum 来代替

4.哈希表统计前缀和余数的出现个数,初始化时需要 hash[0] = 1,代表一个元素也没有时出现的情况为 0 % k = 0

代码:

class Solution {
public:int subarraysDivByK(vector<int>& nums, int k) {unordered_map<int,int> hash;hash[0] = 1;int sum = 0,ret = 0;for(auto x:nums){sum += x;//dp[i] = dp[i-1] + nums[i]int flag = (sum % k + k) % k; if(hash.count(flag)) ret += hash[flag];hash[flag]++;}return ret;}
};

7.连续数组

找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度

1.转化:将所有的 0 修改为 -1 ,那么题目就能修改成 找出最长的子数组,使子数组中所有元素的和为 0 -> 求出和为 0 的最长子数组

2.前缀和思想+哈希表来解决,哈希表存放的时前缀和与前缀和所对应的下标;当有重复的前缀和时,只保留下标靠前的1个;哈希表初始化时,需要 hash[0] = -1,即数组中一个元素都没有时,前缀和为0

3.如果有重复的前缀和,则保留最一开始的一堆键值对,因为该键值对才能得出最优解;如下图2所示,最终结果为x1->x3,因此保留的前缀和下标也应为前一个ret的下标

图1

  图2

代码:

class Solution {
public:int findMaxLength(vector<int>& nums) {for(int i=0;i<=nums.size()-1;i++) if(!nums[i]) nums[i] = -1;int sum = 0, max = 0;unordered_map<int,int> hash;hash[0] = -1;for(int i=0;i<=nums.size()-1;i++){sum+=nums[i];//开始判断if( hash.count(sum) && i-hash[sum] > max) max = i - hash[sum];//第一次遇到,加入键值对if(!hash.count(sum))hash[sum] = i;}return max;}
};

代码易错点:

8.矩阵区域和

返回一个数组,要求数组中的每个下标中的值为原数组同一下标周围k层的所有元素和;如下图所示,k = 1,求数组当中那个下标时,即将上下左右一圈所有的数相和1 + 2 + 3 + 4 + 6 + 7 + 8 +9 = 45 

二维前缀和模板:

1.前缀和创建:dp[i-1][j] + dp[i][j-1] + mat[i][j] - dp[i-1][j-1]

2.前缀和使用:dp[x2][y2] - dp[x2][y1-1] - dp[x1-1][y2] + dp[x1-1][y1-1]


该题当中,假如目标值的下标为 [i,j] ,那么 [x2,y2] 为 [i+k,j+k],[x1,y1] 为 [i-k,j-k]

当 i -  k or j - k 过小时,要回到 0 下标;同理,当 i + k or j + k 过大时,要回到数组最后一行最后一列的元素下标对应值

需要注意的是:无论创建还是使用时,dp表都需要多开一行一列,可以使用 题3 的方法去解决

代码:

class Solution {
public:vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {int m = mat.size(),n = mat[0].size();vector<vector<int>> dp(m+1,vector<int>(n+1));//创建前缀和for(int i = 1;i<=m;i++)for(int j = 1;j<=n;j++)dp[i][j] = dp[i-1][j] + dp[i][j-1] + mat[i-1][j-1] - dp[i-1][j-1];//总逻辑vector<vector<int>> ans(m,vector<int>(n));for(int i=0;i<=m-1;i++)for(int j=0;j<=n-1;j++){//得出x与y,记得使用时ans数组与dp数组的下标对应关系int x1 = max(0,i-k) + 1;int y1 = max(0,j-k) + 1;int x2 = min(m-1,i+k) + 1;int y2 = min(n-1,j+k) + 1;//使用前缀和ans[i][j] = dp[x2][y2] - dp[x2][y1-1] - dp[x1-1][y2] + dp[x1-1][y1-1];}return ans;}
};
http://www.dtcms.com/a/565349.html

相关文章:

  • 冬创网站建设培训中心高端网站建设公司有哪些
  • java面试:有了解过RocketMq架构么?详细讲解一下
  • JAVA国际版同城打车源码同城服务线下结账系统源码适配PAD支持Android+IOS+H5
  • Milvus:数据字段-主字段和自动识别(五)
  • 【深入浅出PyTorch】--8.1.PyTorch生态--torchvision
  • Blender新手入门,超详细!!!
  • Milvus:数据库层操作详解(二)
  • Blender入门学习09 - 制作动画
  • 网站建设终身不用维护网络推广主要内容
  • 金融知识详解:隔日差错处理机制与银行实战场景
  • 网站运营编辑浙江久天建设有限公司网站
  • 做网站销售说辞有赞商城官网登录
  • MATLAB实现基于RPCA的图像稀疏低秩分解
  • 象山企业门户网站建设扬州高端网站制作
  • 服务器网站建设维护app制作定制开发
  • php企业网站开发方案服装外贸网站建设
  • 【Go】--互斥锁和读写锁
  • 《从适配器本质到面试题:一文掌握 C++ 栈、队列与优先级队列核心》
  • 心理咨询网站模板做网站手机
  • 光学3D表面轮廓仪中Rz代表什么?如何精准测量Rz?
  • ps做登录网站北京网站制作工作室
  • git rebase提交
  • vue3引入icon-font
  • 基于开源操作系统搭建K8S高可用集群
  • 学做网站论坛 可以吗做网站是不是太麻烦了
  • leetcode 1578 使绳子变成彩色的最短时间
  • 中国建设银行网上银行官方网站长沙优秀网站建设
  • 1.7 Foundry介绍
  • 什么是向量数据库?主流产品介绍与实战演练
  • redission实现延时队列