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;
    }
}
相关推荐
ZZHow1024几秒前
Maven入门_简介、安装与配置
java·笔记·maven
小蕾Java9 分钟前
Java 开发工具,最新2025 IDEA使用(附详细教程)
java·ide·intellij-idea
Tans518 分钟前
[小笔记] Java 集合类
java
纪元A梦37 分钟前
贪心算法应用:K-Means++初始化详解
算法·贪心算法·kmeans
月阳羊38 分钟前
【硬件-笔试面试题-95】硬件/电子工程师,笔试面试题(知识点:RC电路中的时间常数)
java·经验分享·单片机·嵌入式硬件·面试
Bigemap42 分钟前
BigemapPro快速添加历史影像(Arcgis卫星地图历史地图)
java·开发语言
IT学长编程1 小时前
计算机毕业设计 基于Hadoop的健康饮食推荐系统的设计与实现 Java 大数据毕业设计 Hadoop毕业设计选题【附源码+文档报告+安装调试】
java·大数据·hadoop·毕业设计·课程设计·推荐算法·毕业论文
_不会dp不改名_1 小时前
leetcode_21 合并两个有序链表
算法·leetcode·链表
hrrrrb1 小时前
【Python】字符串
java·前端·python
mark-puls1 小时前
C语言打印爱心
c语言·开发语言·算法