刷穿剑指offer-Day07-数组III 前缀和知识讲解!

简介: 刷穿剑指offer-Day07-数组III 前缀和知识讲解!

昨日回顾


昨天的数组专题,我们针对双指针中的特殊场景----滑动窗口思维进行了学习。

在解题思维中,罗列出了滑动窗口的模板的使用方式,通过:

  1. 确定左右边界
  2. 查找窗口滑动条件的方式
  3. 按照题意套模板

即可可以轻松解决滑窗相关的题目。


滑动窗口的力所不及


在套模板的同时,大家是否考虑过,假设题目同样是求连续的子数组,但是在数组中出现了负数,那这种情况下还可以使用滑动窗口么?

答案是不行的,为什么?

我们窗口滑动的条件是什么,while窗口内元素超过或者不满足条件时移动,但如果数组存在负数,遇到不满足题意的时候,我们应该移动窗口左边界,还是扩大窗口右边界从而寻找到符合条件的情况呢?

当一种场景存在多种可能时,显然就是当前的算法不适配解题。今天就为大家介绍另一种数组中常用的算法----前缀和


前缀和的解题思想


前缀和的题目解题思维比较固定,即当我们循环数组到下标N时,需要用到数组前N-1项的计算的结果(这里不一定非要是和,也可能是积等),此时我们就该考虑是否应该通过计算数组循环过程中的累计值的方式简化解题,如此便有了前缀和的解题思想。

了解了思想,下来就该考虑,这个累计的结果我们该通过什么方式保存起来呢?

  1. 题目明确要求不允许使用额外空间的,直接原地修改数组
  2. 不限制空间复杂度时,最好额外开辟空间计算,避免数据污染
  3. 计算时如果每次只需要获取前一次的累计结果,可以通过数组的方式存储每次获取数组末尾元素的值
  4. 如果每次计算需要获取前几次或更多次的结果进行对比时,推荐哈希表的方式,这样可以压缩时间复杂度

让我们先来通过一道题目,熟悉下前缀和的思维,并且了解下关于前缀和边界的这个易错点。


剑指OfferII010.和为k的子数组


https://leetcode-cn.com/problems/QTMn0o/solution/shua-chuan-jian-zhi-offer-day07-shu-zu-i-jdnu/

难度:中等


题目

给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数。

提示:

  • 1 <= nums.length <= 2 * 10 ^ 4
  • -1000 <= nums[i] <= 1000
  • -10 ^ 7 <= k <= 10 ^ 7


示例

示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2
解释: 此题 [1,1] 与 [1,1] 为两种不同的情况
示例 2 :
输入:nums = [1,2,3], k = 3
输出: 2


分析

这道题目非常简洁,就是求数组中何为整数k的连续子数组个数。

如果这道题的取值没有负数,那就是标准的滑窗问题,但因为有了负数,滑窗思想不能用了。

通过分析,这道题应该属于我们上面列举四种情况的最后一种。具体思路如下:

  1. 初始化一个空的哈希表和pre_sum=0的前缀和变量
  2. 设置返回值ret = 0,用于记录满足题意的子数组数量
  3. 循环数组的过程中,通过原地修改数组的方式,计算数组的累加和
  4. 将当前累加和减去整数K的结果,在哈希表中查找是否存在
  5. 如果存在该key值,证明以数组某一点为起点到当前位置满足题意,ret加等于将该key值对应的value
  6. 判断当前的累加和是否在哈希表中,若存在value+1,若不存在value=1
  7. 最终返回ret即可

但在这里要注意刚才说到的前缀和边界问题。

我们在计算这种场景时,需要考虑如果以数组nums[0]为开头的连续子数组就满足题意呢?

此时候我们的哈希表还是空的,没办法计算前缀和!所以遇到这类题目,都需要在哈希表中默认插入一个{0:1}的键值对,

用于解决从数组开头的连续子数组满足题意的特殊场景。

下面就开始解题吧!


解题


Python:

class Solution:
    def subarraySum(self, nums, k):
        ret = pre_sum = 0
        pre_dict = {0: 1}
        for i in nums:
            pre_sum += i
            ret += pre_dict.get(pre_sum - k, 0)
            pre_dict[pre_sum] = pre_dict.get(pre_sum, 0) + 1
        return ret


Java:

class Solution {
    public int subarraySum(int[] nums, int k) {
        int pre_sum = 0;
        int ret = 0;
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        for (int i : nums) {
            pre_sum += i;
            ret += map.getOrDefault(pre_sum - k, 0);
            map.put(pre_sum, map.getOrDefault(pre_sum, 0) + 1);
        }
        return ret;
    }
}

上面这道题,是一道标准的前缀和题目,并没有过多的去绕弯。

但如果你以为前缀和都是这么赤果果的出题,那真的错了。很多情况下,我们都需要对题目进行变化后,才能使用前缀和思想的。拿接下来这道题目说明。


剑指OfferII011.0和1个数相同的子数组


https://leetcode-cn.com/problems/A1NYOS/solution/shua-chuan-jian-zhi-offer-day07-shu-zu-i-4q9c/

难度:中等


题目

给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

提示:

  • 1 <= nums.length <= 105
  • nums[i] 不是 0 就是 1


示例

示例 1:
输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。
示例 2:
输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量 0 和 1 的最长连续子数组。


分析

如果光看示例容易让我们误以为奇数下标为0,偶数下标为1,或者每两个下标长度判断一次结果等偏激的思想。

但当我们遇到连续0 或者连续1等特殊场景时,明显就不适用了。那么该如何变换后,可以适配前缀和的解题思维呢?

我们不妨将所有0转化为-1,那么如果遇到了相同数量的0和1,累加之后的结果就为0,不是就又转化为前缀和的思想了么?

解题思路如下:

  1. 初始化哈希表,并添加{0:-1}
  2. 声明前缀和变量pre_sum = 0,最大子数组长度返回值ret = 0
  3. 循环数组,若元素为0,pre_sum - 1,反之pre_sum + 1
  4. 判断哈希表中是否存在值为pre_sum的key
  5. 若存在pre_sum的key,更新ret为max(ret, 当前下标 - key对应的value + 1)
  6. 最终返回ret即可


解题


Python:

class Solution:
    def findMaxLength(self, nums: List[int]) -> int:
        pre_dict = {0: -1}
        ret = pre_sum = 0
        for index, num in enumerate(nums):
            pre_sum += -1 if num == 0 else 1
            if pre_sum in pre_dict:
                ret = max(ret, index - pre_dict[pre_sum])
            else:
                pre_dict[pre_sum] = index
        return ret


Java

class Solution {
    public int findMaxLength(int[] nums) {
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, -1);
        int pre_sum = 0;
        int ret = 0;
        for (int i = 0; i < nums.length; i++) {
            pre_sum += nums[i] == 0 ? -1 : 1;
            if (map.containsKey(pre_sum)) {
                ret = Math.max(ret, i - map.get(pre_sum));
            } else {
                map.put(pre_sum, i);
            }
        }
        return ret;
    }
}


前缀和题目推荐


今天的题目学习就到这里,如果大家学有余力,可以看看其他变种的题目:

  • 713.乘积小于K的子数组(前缀积了解一下)
  • 1004.最大连续1的个数 III

或者在力扣题库中选择前缀和标签,来一次通杀。




相关文章
|
11月前
|
人工智能 Ubuntu IDE
【Python】基础:环境配置与基础语法
本文介绍了Python编程语言及其环境配置方法。Python由Guido van Rossum于1991年创建,以其简洁、易学和强大的功能著称。文章详细讲解了Python的主要特点、Windows和Ubuntu下的安装配置步骤、基础语法、控制流、函数、文件操作、模块使用及面向对象编程等内容,帮助读者快速入门Python编程。
304 4
|
消息中间件 Java API
RocketMQ事务消息, 图文、源码学习探究~
介绍 RocketMQ是阿里巴巴开源的分布式消息中间件,它是一个高性能、低延迟、可靠的消息队列系统,用于在分布式系统中进行异步通信。 从4.3.0版本开始正式支持分布式事务消息~ RocketMq事务消息支持最终一致性:在普通消息基础上,支持二阶段的提交能力。将二阶段提交和本地事务绑定,实现全局提交结果的一致性。 原理、流程 本质上RocketMq的事务能力是基于二阶段提交来实现的 在消息发送上,将二阶段提交与本地事务绑定 本地事务执行成功,则事务消息成功,可以交由Consumer消费 本地事务执行失败,则事务消息失败,Consumer无法消费 但是,RocketMq只能保证本地事务
|
10月前
|
自然语言处理 数据可视化 数据安全/隐私保护
基于qwen2.5 Instruct的智能法庭预研
基于Qwen-2.5 Instruct的大模型智能法庭预研,旨在通过智能化手段提高庭审效率、确保司法公正、降低运营成本。核心功能涵盖智能庭审助手、文书生成、案件检索与分析及智能协作平台,利用自然语言处理、多模态融合等技术,实现庭审记录实时生成、法律条款动态匹配、证据多维度解析等,服务于民事、刑事及行政案件。项目注重数据安全与隐私保护,同时规划了智能仲裁平台、跨区域法庭协作等未来扩展方向,为构建高效、公正的智慧司法体系奠定基础。
271 0
|
存储 算法 Java
【算法系列篇】前缀和-1
【算法系列篇】前缀和-1
|
前端开发 开发工具 图形学
PicoVR Unity SDK⭐️三、详解与UI的交互方式
PicoVR Unity SDK⭐️三、详解与UI的交互方式
|
Kubernetes API 调度
Kubernetes详解(十四)——Pod对象生命周期
Kubernetes详解(十四)——Pod对象生命周期
196 2
|
程序员 编译器 容器
【C++14保姆级教程】变量模板,Labmda泛型
【C++14保姆级教程】变量模板,Labmda泛型
368 0
|
Java 数据库 Maven
基于springboot颐养中心商城前台系统(springboot+mybatis+maven+html+jquery+css)
基于springboot颐养中心商城前台系统(springboot+mybatis+maven+html+jquery+css)
161 0
|
关系型数据库 MySQL 测试技术
探索MySQL间隙锁的奥秘
MySQL中的间隙锁(Gap Lock)是一种锁机制,用于在多个事务中保护数据的一致性。它主要用于防止并发事务插入新数据或者修改已有数据时,导致其他事务读取到不一致的结果。
探索MySQL间隙锁的奥秘

热门文章

最新文章