刷穿剑指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

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

欢迎关注我的公众号: 清风Python,带你每日学习Python算法刷题的同时,了解更多python小知识。

我的个人博客:https://qingfengpython.cn

力扣解题合集:https://github.com/BreezePython/AlgorithmMarkdown

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容

  • 昨日回顾 上一篇文章,我们讲解了数据结构的分类,并对于学习到的第一种数据结构--数组进行了简单介绍。 在介绍解题时...
    清风Python阅读 268评论 0 2
  • 前文回顾 之前我们使用三天的时间,学习了整数章节的知识学习。并在Day4的总结中,结合读者们关于算法学习和刷题过程...
    清风Python阅读 86评论 0 1
  • 昨日回顾 昨天作为这个系列的第一篇文章,分别介绍了Java与Python的以下内容: 整数类型与考点 二进制与十进...
    清风Python阅读 294评论 0 1
  • to-do:看一下别人写的题解 https://github.com/981377660LMT/algorithm...
    winter_sweetie阅读 740评论 1 0
  • day5 50.第一个只出现一次的字符(重点) 思路:哈希表遍历两次字符串s,第一遍统计各个字符的出现次数,第二遍...
    IAmKepler阅读 262评论 0 0