LeetCode算法日记 - Day 15: 和为 K 的子数组、和可被 K 整除的子数组

目录

[1. 和为 K 的子数组](#1. 和为 K 的子数组)

[1.1 题目解析](#1.1 题目解析)

[1.2 解法](#1.2 解法)

[1.3 代码实现](#1.3 代码实现)

[2. 和可被 K 整除的子数组](#2. 和可被 K 整除的子数组)

[2.1 题目解析](#2.1 题目解析)

[2.2 解法](#2.2 解法)

[2.3 代码实现](#2.3 代码实现)


1. 和为 K 的子数组

560. 和为 K 的子数组 - 力扣(LeetCode)

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

1.1 题目解析

这是一个典型的「连续区间和统计」问题,本质上就是在数组中寻找 有多少个连续子数组的和等于 k。换句话说,它是「区间和查询」的一种变形,只不过不是求和,而是数满足条件的区间个数。

常规解法

最直观的方法是用双层循环:

  • 外层枚举子数组的起点 i

  • 内层枚举子数组的终点 j,计算 [i, j] 的和并判断是否等于 k。

这种方法简单粗暴,但时间复杂度是 O(n²),在最坏情况下数组长度 2 * 10^4,会有 4 亿次计算,无法在合理时间内完成。

问题的核心在于:我们在计算子数组和时重复了大量运算(比如 [0, 3] 和 [0, 4],它们都需要重新计算 [0, 3] 的和)。要想高效,就必须避免重复计算。

思路转折

要想避免重复计算,就要 预处理区间和

区间和问题最常见的优化就是 前缀和

  • 设 sum 表示 [0, i] 的元素和。

  • 那么子数组的和 = sum - k,所以,只要知道之前有多少个前缀和等于 sum[i] - k,就能统计出以 i 结尾的子数组个数。

1.2 解法

用哈希表记录「某个前缀和出现了几次」。

在遍历数组、计算前缀和的过程中,随时查询 sum - k是否出现过,并累加到答案。

**i)**初始化哈希表:hash.put(0, 1),表示前缀和为 0 出现过一次(相当于前缀和数组的虚拟起点)。

**ii)**遍历数组,累计前缀和 sum。

**iii)**查询 sum - k 是否存在于哈希表中,若存在则加上对应次数。

**iiii)**更新当前前缀和 sum 在哈希表中的出现次数。

**iiiii)**遍历结束,返回答案。

1.3 代码实现

java 复制代码
class Solution {
    public int subarraySum(int[] nums, int k) {
        // 哈希表:存前缀和 -> 出现次数
        Map<Integer, Integer> hash = new HashMap<>();
        hash.put(0, 1); // 模拟前缀和数组 sum[0] = 0

        int sum = 0, ans = 0;
        for (int x : nums) {
            sum += x; // 累加前缀和

            // 如果 sum - k 出现过,说明存在子数组和为 k
            ans += hash.getOrDefault(sum - k, 0);

            // 更新哈希表,记录当前前缀和出现次数
            hash.put(sum, hash.getOrDefault(sum, 0) + 1);
        }
        return ans;
    }
}

2. 和可被 K 整除的子数组

974. 和可被 K 整除的子数组 - 力扣(LeetCode)

给定一个整数数组 nums 和一个整数 k ,返回其中元素之和可被 k 整除的非空 子数组 的数目。

子数组 是数组中 连续 的部分。

示例 1:

复制代码
输入:nums = [4,5,0,-2,-3,1], k = 5
输出:7
解释:
有 7 个子数组满足其元素之和可被 k = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]

示例 2:

复制代码
输入: nums = [5], k = 9
输出: 0

提示:

  • 1 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • 2 <= k <= 104

2.1 题目解析

本质上,这是一个 "区间和是否满足某个整除条件" 的问题。

换句话说,我们要统计 有多少个子数组的和能被 K 整除

常规解法

  • 枚举所有子数组(两层循环),

  • 计算每个子数组的和,

  • 判断是否能被 K 整除。

时间复杂度:

  • 枚举子数组 → O(n²)

  • 每次计算子数组和 → O(1)(如果用前缀和优化),否则 O(n)

  • 总体:O(n²) ~ O(n³),对于 n=3*10⁴ 肯定超时

思路转折

所以问题就转化为:统计前缀和的余数相等的情况, 要想高效 → 必须避免 O(n²) 枚举。

我们可以在遍历数组的同时:

  • 维护当前前缀和对 k 的余数,

  • 统计这个余数出现过多少次。

这样,每次遇到相同余数,就代表多出一些合法子数组

2.2 解法

前缀和 + 同余定理 + 哈希表计数

**i)**初始化哈希表:hash.put(0, 1)(默认前缀和为 0 出现一次)。

**ii)**遍历数组 nums:

  • 累加前缀和 sum

  • 计算余数 r = (sum % k + k) % k(保证非负)

  • 统计已有多少前缀和的余数也等于 r → 加入结果

  • 更新哈希表:hash[r]++

**iii)**返回结果。

2.3 代码实现

java 复制代码
class Solution {
    public int subarraysDivByK(int[] nums, int k) {
        Map<Integer, Integer> hash = new HashMap<>();
        hash.put(0, 1); // 前缀和为0的余数,出现1次
        int sum = 0, ret = 0;

        for (int x : nums) {
            sum += x; 
            int r = (sum % k + k) % k; // 修正负数余数
            ret += hash.getOrDefault(r, 0); // 找到已有多少相同余数
            hash.put(r, hash.getOrDefault(r, 0) + 1); // 更新出现次数
        }

        return ret;
    }
}
相关推荐
Jayyih3 分钟前
嵌入式系统学习Day19(数据结构)
数据结构·学习
renhongxia114 分钟前
大模型微调RAG、LORA、强化学习
人工智能·深度学习·算法·语言模型
计算机程序员小杨20 分钟前
计算机专业的你懂的:大数据毕设就选贵州茅台股票分析系统准没错|计算机毕业设计|数据可视化|数据分析
java·大数据
y1y1z24 分钟前
EasyExcel篇
java·excel
DokiDoki之父43 分钟前
多线程—飞机大战排行榜功能(2.0版本)
android·java·开发语言
DdduZe44 分钟前
8.19作业
数据结构·算法
PyHaVolask1 小时前
链表基本运算详解:查找、插入、删除及特殊链表
数据结构·算法·链表
高山上有一只小老虎1 小时前
走方格的方案数
java·算法
whatever who cares1 小时前
Java 中表示数据集的常用集合类
java·开发语言
吧唧霸1 小时前
golang读写锁和互斥锁的区别
开发语言·算法·golang