LeetCode hot 100 —560. 和为 K 的子数组

给你一个整数数组 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

不知道为什么做了滑动窗口和双指针以后,总想用left and right来做很多题目

但在第一版本敲代码的时候就知道这里不适合用了。

首先是数组是无序的,左移和右移并不能完全的对应减和加;所以带来的衍生点是,比如说现在找到了一个合适的子数组,我的Left和 right 要怎么更新我完全没有头绪


然后我想到了 **差分。**也是当时练习csp的入门算法。

和GPT对了一下思路,除了差分,还有一个东西叫 **前缀和,**算是差分的进阶版(太久没用了已经不是我的第一反应了....),也就是前n个数字的和是多少

假设定义:

复制代码
prefix[i] = nums 前 i 个数字之和

那么从下标 left 到 right 的子数组和可以表示成:

复制代码
prefix[right + 1] - prefix[left]

题目要求它等于 k:

复制代码
prefix[right + 1] - prefix[left] = k

把这个式子移动一下:

复制代码
prefix[left] = prefix[right + 1] - k

现在先别急着写代码,只思考:当我从左向右计算到一个新的前缀和 current 时,我需要在之前出现过的前缀和里寻找什么值?(前缀和为current - k的)再进一步思考:题目问的是子数组的"个数",如果要找的那个前缀和值在前面出现了多次,意味着什么?

尝试手算:

复制代码
nums = [1, -1, 1]
k = 1

它的前缀和(记得最开始还有一个"尚未选择任何数字"的前缀和 0)是:

复制代码
0, 1, 0, 1

当你走到最后一个前缀和 1 时,需要寻找:

复制代码
1 - k = 0

此前 0 出现了不止一次。每一个此前出现的 0,都对应一个以当前位置结尾、和为 1 的子数组。

因此你下一步只需要回答两个问题:

  1. 如何一边遍历,一边记录此前每个前缀和出现了多少次?
  2. 如何快速查询 当前前缀和 - k 此前出现了多少次?

想清楚这两点,就不再需要控制 left 和 right 了。这里的思维转换是:不要直接维护一个窗口,而是把每个可能的左边界压缩成"此前出现过的前缀和统计"。


最后的代码如下:

python 复制代码
class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        # 构造前缀和
        prefix_cnt = {0: 1}  
        """
        这个也算是看了GPT给的答案以后受到的大启发,我给的一个注释和思维解释是:
            这道题目需要前缀和,但为什么不使用常规的前缀和数组呢?
                - 因为我最后需要统计的是res_count,也就是我需要统计的是个数

            我知道了当前的前缀和为current, 我需要找的值是current - k,我需要统计的东西是current - k 出现了多少次,所以这里用的是一个dict的形式,可以更快的知道某个前缀和已经出现的次数.
                - 为什么要初始化一个0 : 1:确保nums[0]也可以被划入. 比如当前current == k,那么就是nums[0:index + 1]可以计入一次,但是如果没有0,这个答案不会被计入。
        """
        res = 0
        prefix_sum = 0 #记录前缀和

        for num in nums:
            prefix_sum += num
            res += prefix_cnt.get(prefix_sum - k, 0)
            """
            dict.get(key, default) 表示:
                - 如果字典中存在 key,返回对应的值。
                - 如果不存在,返回 default。
            """

            prefix_cnt[prefix_sum] = (
                prefix_cnt.get(prefix_sum, 0) + 1
            )
        return res

总结一下,感觉GPT启发的核心是:

先找准算法结构,用什么。双指针,还是我想到的差分,及其进阶版的前缀和?

从答案反推数据结构,数据结构是统计上面的优化,什么样的数据结构载体可以更快的得到答案?(比如这里如果用前缀和数组的话,每遇到一个前缀和就要去统计current - k的出现次数,时间开销肯定会加大的)

相关推荐
从琳开始98915 小时前
从Socket到UDP回显服务器:Java网络编程完整指南
java·网络·网络协议·udp·java-ee
SZYSK15 小时前
OpenHarmony 上读卡、推理和双屏刷新抢 CPU:核验机与消费机的调度差
物联网·算法·harmonyos·1024程序员节
Ivanqhz15 小时前
MoE + LLM 应用
数据库·人工智能·pytorch·python·深度学习·算法·1024程序员节
西瓜拿铁好喝16 小时前
开源和闭源 AI 办公工具,分别适合谁
java·前端·数据库
学长毕设16 小时前
基于SpringBoot的校园礼服租赁系统的设计与实现
java·spring boot·后端
lpfasd12316 小时前
推荐算法进化史
算法·机器学习·推荐算法
明天也要努力学习16 小时前
深入理解 JVM 垃圾回收(GC)
java·jvm
Pniubi16 小时前
力扣543二叉树的直径(深度优先搜索法(递归))
算法·leetcode·职场和发展
golang学习记16 小时前
IDEA 断点调试技巧:拖拽
java·ide·intellij-idea
Sayai16 小时前
Flink 日志解析任务性能优化实战:正则/Grok 预编译、Guava 缓存权重陷阱与 ES 写入隔离
java·大数据·性能优化·flink·实时计算