算法刷题总结 (八) 前缀和

news2025/1/14 2:24:05

算法总结8 前缀和

  • 一、前缀和的概念
    • 1.1、什么是前缀和?
    • 1.2、常见类型
      • 1.2.1、求数组前i个数之和
      • 1.2.2、求数组的区间和
  • 二、经典例题
      • 2.1、求数组前i个数之和
        • 560. 和为 K 的子数组 - 前缀和+哈希表
        • 525. 连续数组
      • 2.2、求数组的区间和
        • 303. 区域和检索 - 数组不可变
        • 643. 子数组最大平均数 I - (隐藏的前缀和)
        • 304. 二维区域和检索 - 矩阵不可变 - (二维区间)
  • 参考

在这里插入图片描述

一、前缀和的概念

1.1、什么是前缀和?

前缀和,英文是 preSum。是面试中经常考到的题目,并且难度也不大,我们直接从题目入手开始讲解。

首先我们看看一道很经典的简单题 1480. 一维数组的动态和,在没接触过前缀和的时候,我们对这道题的解法,很可能采用双重循环:

class Solution:
    def runningSum(self, nums: List[int]) -> List[int]:
        preSum = []
        # 选取每一个索引,当做下一层循环的结尾
        for i in range(len(nums)):
            # 用来记录总和
            su_m = 0
            # 从0开始到i求和
            for j in range(i+1):
                su_m+=nums[j]
            # 添加到结果
            preSum.append(su_m)
        return preSum

又或者我们取消内层循环,直接sum对切片求和与存储:

class Solution:
    def runningSum(self, nums: List[int]) -> List[int]:
        preSum = []
        # 选取每一个索引,当做下一层循环的结尾
        for i in range(len(nums)):
            # 用sum函数对切片求和
            preSum.append(sum(nums[:i+1]))
        return preSum

这种直接求和是很简单直接的,但是它的效率是非常低的,因为我们没有利用到一个条件,也就是值之间的关系,即preSum的第i个值取决于第i-1个值:

输入:nums = [1,2,3,4]
输出:[1,3,6,10]
解释:动态和计算过程为 [1, 1+2, 1+2+3, 1+2+3+4]

1 这个值的索引为第一个,前面没有值,直接等于当前nums的1
1+2 = 前一个1 加上 当前的nums上的2
1+2+3 = 前一个1+2 加上 当前nums上的3
1+2+3+4 = 前一个1+2+3 加上 当前nums上的4

有了这个规律我们可以很轻易的写出:

class Solution:
    def runningSum(self, nums: List[int]) -> List[int]:
        for i in range(1, len(nums)):
            nums[i] = nums[i]+nums[i-1]
        return nums

我们可以写的更加规范一点:

class Solution:
    def runningSum(self, nums: List[int]) -> List[int]:
    	# 使用新数组去存储结果
    	preSum = []
    	# 把索引0加入其中
        for i in range(len(nums)):
        	if i==0:
        		preSum.append(nums[i)
        	else:
            	nums[i] = nums[i]+preSum[i-1]
        return nums

如果理解了上面的内容,那么我告诉你,preSum 数组其实就是「前缀和」。

「前缀和」 是从 nums 数组中的第 0 位置开始累加,到第 i 位置的累加结果,我们常把这个结果保存到数组 preSum 中,记为 preSum[i]。

在前面计算「前缀和」的代码中,计算公式为 preSum[i] = preSum[i - 1] + nums[i] ,为了防止当 i = 0 的时候数组越界,所以加了个 if (i == 0) 的判断,即 i == 0 时让 preSum[i] = nums[i]。

在其他常见的写法中,为了省去这个 if 判断,我们常常把「前缀和」数组 preSum 的长度定义为 原数组的长度 + 1。preSum 的第 0 个位置,相当于一个占位符,置为 0。
那么就可以把 preSum 的公式统一为 preSum[i] = preSum[i - 1] + nums[i - 1],此时的preSum[i]表示nums中 i 元素左边所有元素之和(不包含当前元素 i)。

下面以 [1, 12, -5, -6, 50, 3] 为例,用动图讲解一下如何求 preSum:

请添加图片描述
上图表示:
preSum[0] = 0;
preSum[1] = preSum[0] + nums[0];
preSum[2] = preSum[1] + nums[1];



1.2、常见类型

一般分为两种类型:

类型一类型二
求数组前 i 个数之和求数组的区间和

1.2.1、求数组前i个数之和

求数组前 i 个数之和,是「前缀和」数组的定义,所以是最基本的用法。
举例而言:

  • 如果要求 nums 数组中的前2个数的和,即sum(nums[0], nums[1]),直接返回 preSum[2]即可
  • 同理,如果要求 nums 数组中所有元素的和,即sum(nums[0], …, nums[length-1]),直接返回preSum[length]即可

1.2.2、求数组的区间和

利用 preSum 数组,可以在 O(1) 的时间内快速求出 nums 任意区间 [i,j] (两端都包含) 内的所有元素之和。

公式为:sum(i,j) = preSum[j+1] - preSum[i]

什么原理呢?其实就是消除公共部分,即0-i-1部分的和,那么就能得到 i-j 部分的区间和。

注意上面的式子,使用的是 preSum[j+1] 和preSum[i]:

  • preSum[j+1] 表示的是 nums 数组中 [0, j] 的所有数字之和(包含0和j)
  • preSum[i] 表示的是 nums 数组中[0, i-1]的所有数字之和(包含0和i-1)
  • 两者相减时,结果留下了 nums 数组中 [i, j] 的所有数字之和


二、经典例题

2.1、求数组前i个数之和

560. 和为 K 的子数组 - 前缀和+哈希表

560. 和为 K 的子数组

from collections import defaultdict
class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        preSum = defaultdict(int)
        preSum[0]=1
        presum = 0
        count=0
        for i in range(len(nums)):
            presum+=nums[i]
            
            count+=preSum[presum-k]
            preSum[presum]+=1
        return count

525. 连续数组

525. 连续数组

我们以nums的前缀和来构造一个hashmap,并且该hashmap不能改变,只能增加(保留第一次出现的index,第二次出现则做ans的max保留),因为它是记录第一次出现的位置,当第二次出现key时则做相应索引的减法,即为结果长度,该长度中0和1数量相同,举例说明:
0 1 0 0 1 0
-1 0 -1 -2 -1

indexnumspre_sumhashmap{0:-1}ans
00-1hashmap找不到-1,增加:{0:-1, -1:0}0
110hashmap能找到0,求ans1-(-1)=2
20-1hashmap能找到-1,求ans2-0=2
30-2hashmap找不到-2,增加:{0:-1, -1:0, -2:3}2(保留最大值)
41-1hashmap能找到-1,求ans4-0=4
50-2hashmap能找到-2,求ans5-3=2, 但4(保留最大值)

思考:为什么第二次出现的值,即便不是0,而是-1或-2,也都能计算ans?
第二次出现说明这两个值之间的累加和为0,也就是0和1数量相等,我们需要关注的是两个前缀和的差值之间的累加和是否为0,而不是只看前缀和的值,这是无意义的,因为前缀和只能代表该值前面所有值累加的特征值。

class Solution:
    def findMaxLength(self, nums: List[int]) -> int:
        # 前缀和字典: key为1的数量和0的数量的差值,value为对应坐标
        hashmap = {0:-1}
        # 当前1的数量和0的数量的差值
        ans = pre_sum = 0

        for i,num in enumerate(nums):
            # 每多一个1,差值+1,每多一个0,差值-1
            pre_sum+=1 if num else -1
            # 如果存在1和0的数量差值相等的地方,那么说明后者到前者之前1和0的数量相等!
            if pre_sum in hashmap:
                ans = max(ans, i-hashmap[pre_sum])
            # 在hashmap里面就进行比较,不在则存储
            else:
                hashmap[pre_sum]=i
        return ans

2.2、求数组的区间和

在这里插入图片描述

303. 区域和检索 - 数组不可变

通过 1.2.2、求数组的区间和 的内容可以秒杀这道题。

class NumArray:

    def __init__(self, nums: List[int]):
        self.preSum = [0]
        for i in range(len(nums)):
            self.preSum.append(self.preSum[i]+nums[i]) 

    def sumRange(self, left: int, right: int) -> int:
        return self.preSum[right+1]-self.preSum[left]

# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# param_1 = obj.sumRange(left,right)

643. 子数组最大平均数 I - (隐藏的前缀和)

643. 子数组最大平均数 I
这道题可以用前缀和解,也可以用固定大小为k的滑动窗口来解决

解法1:前缀和:
要求大小为k的窗口内的最大平均数,可以求[i, i+k]区间的最大和再除以k,即要求(preSum[i+k]- preSum[i])/k 的最大值。总之题目要求 区间和 的时候,那么就可以考虑使用 前缀和。

class Solution:
    def findMaxAverage(self, nums: List[int], k: int) -> float:
        preSum = [0]
        for i in range(len(nums)):
            preSum.append(preSum[i]+nums[i])
        max_avg = -float('inf')
        for i in range(len(nums)-k+1):
            max_avg = max(max_avg, (preSum[i+k]-preSum[i])/k)
        return max_avg

解法二:滑动窗口
滑动窗口类型的题解思路可以看看这篇文章:算法刷题总结 (七) 双指针

class Solution:
    def findMaxAverage(self, nums: List[int], k: int) -> float:
        left,right = 0, k-1
        avg = sum(nums[left:right+1])
        max_avg = avg
        for i in range(k, len(nums)):
            avg+=nums[i]-nums[i-k]
            max_avg = max(avg, max_avg)
        return max_avg/k

在这里插入图片描述
在这里插入图片描述

304. 二维区域和检索 - 矩阵不可变 - (二维区间)

304. 二维区域和检索 - 矩阵不可变

当「前缀和」拓展到二维区间时,可以用下面的思路求解:

(1)步骤一:求 preSum
我们定义 p r e S u m [ i + 1 ] [ j + 1 ] preSum[i+1][j+1] preSum[i+1][j+1] ,即同前面,row与col都增加一位。第一行和第一列为占位符,值为0。从 [ 1 ] [ 1 ] [1][1] [1][1] [ r o w + 1 ] [ c o l + 1 ] [row+1][col+1] [row+1][col+1]表示矩阵 m a t r i x [ 0 ] [ 0 ] matrix[0][0] matrix[0][0] [ r o w ] [ c o l ] [row][col] [row][col]的前缀面积和。

递推公式为:
p r e S u m [ i + 1 ] [ j + 1 ] = p r e S u m [ i ] [ j + 1 ] + p r e S u m [ i + 1 ] [ j ] − p r e S u m [ i ] [ j ] + m a t r i x [ i ] [ j ] preSum[i+1][j+1]=preSum[i][j+1]+preSum[i+1][j]-preSum[i][j]+matrix[i][j] preSum[i+1][j+1]=preSum[i][j+1]+preSum[i+1][j]preSum[i][j]+matrix[i][j]

可以用下图帮助理解:
S ( O , D ) = S ( O , C ) + S ( O , B ) − S ( O , A ) + D S(O,D)=S(O,C)+S(O,B)-S(O,A)+D S(O,D)=S(O,C)+S(O,B)S(O,A)+D

在这里插入图片描述
减去 S ( O , A ) S(O,A) S(O,A)的原因是 S ( O , C ) S(O,C) S(O,C) S ( O , B ) S(O,B) S(O,B)中都有 S ( O , A ) S(O,A) S(O,A),即加了两次 S ( O , A ) S(O,A) S(O,A),所以需要减去一次 S ( O , A ) S(O,A) S(O,A)

<br

(2)步骤二:根据 preSum 求子矩阵面积

前面已经跟你求出了数组从 [ 0 , 0 ] [0, 0] [0,0]位置到 [ i , j ] [i, j] [i,j]位置的 p r e S u m preSum preSum

如果要求 [ r o w 1 , c o l 1 ] [row1, col1] [row1,col1] [ r o w 2 , c o l 2 ] [row2, col2] [row2,col2]的子矩阵的面积的话,用 preSum计算时,对应了以下的递推公式:
p r e S u m [ r o w 2 + 1 ] [ c o l 2 + 1 ] − p r e S u m [ r o w 2 + 1 ] [ c o l 1 ] − p r e S u m [ r o w 1 ] [ c o l 2 + 1 ] + p r e S u m [ r o w 1 ] [ c o l 1 ] preSum[row2+1][col2+1]-preSum[row2+1][col1]-preSum[row1][col2+1]+preSum[row1][col1] preSum[row2+1][col2+1]preSum[row2+1][col1]preSum[row1][col2+1]+preSum[row1][col1]

同样利用一张图来说明:

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

在这里插入图片描述
加上子矩阵 S ( O , G ) S(O,G) S(O,G)面积的原因是 S ( O , E ) S(O,E) S(O,E) S ( O , F ) S(O,F) S(O,F)中都有 S ( O , G ) S(O,G) S(O,G),即减了两次 S ( O , G ) S(O,G) S(O,G),所以需要加上一次 S ( O , G ) S(O,G) S(O,G)

整理清楚后,整体的代码如下:

class NumMatrix:

    def __init__(self, matrix: List[List[int]]):
        row, col = len(matrix), len(matrix[0])
        self.preSum = [[0]*(col+1) for _ in range(row+1)]
        for i in range(row):
            for j in range(col):
                self.preSum[i+1][j+1] = self.preSum[i][j+1]+self.preSum[i+1][j]-self.preSum[i][j]+matrix[i][j]

    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
        return self.preSum[row2+1][col2+1]-self.preSum[row2+1][col1]-self.preSum[row1][col2+1]+self.preSum[row1][col1]




# Your NumMatrix object will be instantiated and called as such:
# obj = NumMatrix(matrix)
# param_1 = obj.sumRegion(row1,col1,row2,col2)


参考

面试题|秋招必会的算法题——前缀和
525.连续数组 前缀和+哈希表 速解!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/444037.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学系统集成项目管理工程师(中项)系列06b_信息系统安全管理(下)

1. 物理安全管理 1.1. 计算机机房与设施安全 1.1.1. 计算机机房 1.1.1.1. 机房场地选择 1.1.1.2. 机房空调、降温 1.1.1.2.1. 基本温度要求 1.1.1.2.1.1. 应有必要的空调设备&#xff0c;使机房温度达到所需的温度要求 1.1.1.2.2. 较完备空调系统 1.1.1.2.2.1. 应有较完…

C语言——隐式转换

目录 前言 隐式转换 1.整型提升 2.算数转换 前言 这里小编给大家简单的补充一下&#xff0c;一些有关C语言的知识点 隐式转换 由于转换形式的不同类型转换这里一共分为整型提升和运算转换两种形式 1.整型提升 在了解整型提升之前&#xff0c;这里我们需要先了解一下截断…

自媒体助手软件开发需具备哪些功能?

自媒体助手软件开发需具备哪些功能&#xff1f; 1、多平台多账号管理。 用户可以在单独的平台上管理其他平台的账号&#xff0c;不需要登录多个平台&#xff0c;为用户减少了大量的时间&#xff0c;与此同时可以记忆账号和密码&#xff0c;提供分组管理&#xff…

【C进阶】详解预处理指令

文章目录 预定义符号#define#define定义标识符#define定义宏#define替换规则#和##带副作用的宏参数宏和函数对比#undef命令行定义 条件编译文件包含头文件被包含的方式嵌套文件包含 其他预处理指令总结 预定义符号 __FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DA…

解码星地一体农机导航:无网作业,极致

星地一体系列农机导航最强大之处是在全国任何地方都拥有信号&#xff0c;即使是在偏远的戈壁滩也能作业&#xff0c;因为星地一体系列导航采用星地融合技术&#xff0c;彻底解决信号问题&#xff0c;通过卫星播发差分信号的服务&#xff0c;在中国&#xff0c;只要可见卫星&…

Windows下如何查看某个端口被谁占用被杀死占用进程

开发时经常遇到端口被占用的情况&#xff0c;这个时候我们就需要找出被占用端口的程序&#xff0c;然后结束它&#xff0c;本文为大家介绍如何查找被占用的端口。 1、打开命令窗口(以管理员身份运行) 开始—->运行—->cmd&#xff0c;或者是 windowR 组合键&#xff0c;…

Win10桌面我的电脑怎么调出来?最简单方法教学

Win10桌面我的电脑怎么调出来&#xff1f;有用户发现自己的电脑桌面没有我的电脑这个程序图标&#xff0c;每次要访问磁盘的时候&#xff0c;开启都非常的麻烦。那么怎么将这个图标设置到桌面显示呢&#xff1f;接下来我们一起来看看以下的解决方法吧。 方法一&#xff1a; 在开…

论文中参考文献的引用

论文中参考文献的引用 写在最前面删除特定格式的数字&#xff08;带小中大括号等等&#xff09;效果如下 设置参考文献格式设置编号格式设置段落格式效果 使用交叉引用去引用这些编号在需要插入参考文南引用的地方使用“交叉引用”快捷键进阶:为“交叉引用”设置快捷键 写在最前…

maven安装及配置IDEA

文章目录 下载下载完成后 解压可以得到如下的目录项 配置环境变量输入下列命令 出现 maven版本表示环境变量配置成功 配置本地仓库、镜像仓库、jdk版本配置本地仓库配置镜像仓库配置jdk版本 IDEA配置maven配置当前项目的maven配置其他项目的maven 下载 https://maven.apache.o…

威联通NAS文件共享 - 搭建SFTP服务并内网穿透实现在外远程访问

文章目录 前言1. 威联通NAS启用SFTP2. 测试局域网访问3. 内网穿透3.1 威联通安装cpolar内网穿透3.2 创建隧道3.3 测试公网远程访问 4. 配置固定公网TCP端口地址4.1 保留一个固定TCP端口地址4.2 配置固定TCP端口地址4.3 测试使用固定TCP端口地址远程连接威联通SFTP 转载自远程内…

Java web 项目 和 java 项目的区别

一、Java Web项目 和 java项目区别 1. Java Web项目是基于Java EE类的&#xff1b;而Java项目是基于Java应用程序的。 2. Java Web项目是网页的编码&#xff0c;像jsp,servlet,struts这类的&#xff0c;而java项目是AWT,SWING这类的编码。 3. Java Web项目中的JAVA文件是tomcat…

【CocosCreator入门】CocosCreator组件 | Layout(布局)组件

Cocos Creator 是一款流行的游戏开发引擎&#xff0c;具有丰富的组件和工具&#xff0c;其中的Layout组件是一种用于实现节点自适应布局的重要组件。它可以根据不同的布局方式&#xff0c;自动调整子节点的位置和大小&#xff0c;从而实现节点的自适应布局。 目录 一、组件介绍…

准备换工作的看过来~

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;得准备面试了&#xff0c;又不知道从何下手&#xff01;为了帮大家节约时间&#xff0c;特意准备了一份面试相关的资料&#xff0c;内容非常的全面&#xff0c;真的可以好好补一补&#xff0c;希望大家在都能拿到理想…

运行时内存数据区之方法区(二)

方法区的演进细节 首先明确&#xff1a;只有HotSpot才有永久代。BEA JRockit、IBMJ9等来说&#xff0c;是不存在永久代的概念的。原则上如何实现方法区属于虚拟机实现细节&#xff0c;不受《]Va虚拟机规范》管束&#xff0c;并不要求统一。Hotspot中方法区的变化&#xff1a; …

Spring核心-IoC控制反转详解 (典藏版)

文章目录 1.IoC容器和Bean介绍2.Spring 中的 IoC 容器2.1 BeanFactory和ApplicationContext概述2.2 BeanFactory2.3 ApplicationContext2.4 BeanFactory vs ApplicationContext2.5 容器的初始化2.6 配置元数据2.6.1 基于XML的容器配置2.6.2 基于注解的容器配置2.6.3 基于Java类…

Junit概述和快速入门

单元测试概述 在程序中&#xff0c;一个单元可以是一个完整的模块&#xff0c;但它通常是一个单独的方法或者程序 在面向对象的编程中&#xff0c;一个单元通常是整个界面&#xff0c;例如类&#xff0c;但可能是单个方法 JUnit是一个java编程语言的单元测试框架 通过先为最…

教育大数据总体解决方案(4)

组件配置 对组件中的项目配置项进行管理&#xff0c;包括节点内容、磁盘空间等等。每一次的配置都以一个配置版本的形式进行保存&#xff0c;用户可选择对应版本的查看对应的配置信息。 测度 对组件内的相关服务指标以图标形式进行状态呈现。可选择相应时间段&#xff0c;查看对…

scala闭包与柯里化

目录 通过闭包实现一个通用的两数相加函数简化柯里化 闭包&#xff1a;如果一个函数&#xff0c;访问到了它的外部&#xff08;局部&#xff09;变量的值&#xff0c;那么这个函数和他所处的环境&#xff0c;称为闭包 通过闭包实现一个通用的两数相加函数 这里内层函数访问了外…

【基础】Kafka -- 主题与分区

Kafka -- 主题与分区 主题的管理创建主题简单创建与查看指定分区副本分配创建指定参数创建 查看主题主题的简单查看带附加功能的查看 修改主题修改分区修改配置 删除主题 主题配置管理配置查看与变更配置查看配置变更 主题端参数 KafkaAdminClient 主题管理基本使用创建主题查看…

【创作赢红包】SQL Server之索引设计

SQL Server之索引设计 一、前言二、索引设计背景知识2.1、索引设计策略包括的任务 三、常规索引设计3.1、数据库注意事项3.2、查询注意事项3.3、列注意事项3.4、索引的特征3.5、索引排序顺序设计指南 总结 一、前言 索引设计不佳和缺少索引是提高数据库和应用程序性能的主要障…