【优选算法必刷100题】第029~30题(前缀和算法):和为 K 的子数组、和可被K整除的子数组

【优选算法必刷100题】第029~30题(前缀和算法):和为 K 的子数组、和可被K整除的子数组

🔥艾莉丝努力练剑:个人主页

专栏传送门:《C语言》《数据结构与算法》C/C++干货分享&学习过程记录Linux操作系统编程详解笔试/面试常见算法:从基础到进阶

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬艾莉丝的简介:



🎬艾莉丝的算法专栏简介:


目录

029  和为 K 的子数组

1.1  算法思路:前缀和+哈希表~>将前缀和存在哈希表中

1.2  算法实现

1.2.1  C++实现

1.2.2  Java实现

1.3  博主手记

030  和可被K整除的子数组

2.1  解法一:暴力枚举

2.2  解法二:前缀和(余数)存在哈希表中

2.2.1  补充知识

2.2.1.1  (数学公式)同余定理

2.2.1.2  c++中负数取模的结果,以及如何修正【负数取模】的结果

2.2.2  算法思路

2.3  算法实现

2.3.1  C++实现

2.3.2  Java实现

2.4  博主手记

结尾


029  和为 K 的子数组

力扣链接:560. 和为 K 的子数组

力扣题解链接:前缀和 + 哈希表解决【和为 K 的子数组】问题

题目描述:

1.1  算法思路:前缀和+哈希表~>将前缀和存在哈希表中

参考下图:

设为数组中的任意位置,用sum[ i ]表示[0 , i]区间内所有元素的和。

如果想知道有多少个【以为结尾的和为的子数组】,就要找到有多少个起始位置为x1,x2,x3...使得[x , i]区间内的所有元素的和为k。那么[0 , x]区间内的和是不是就是sum[ i ] - k了。于是问题就变成——

找到在[0 , i - 1]区间内,有多少前缀和等于sum[ i ] - k的即可。

我们不需要真的去初始化一个前缀和数组,因为我们只关心在 i 位置之前,有多少个前缀和会等于sum[ i ] - k。因此,我们仅需用一个哈希表,一边求当前位置的前缀和,一边存下之前每一种前缀和出现的次数。

1.2  算法实现

1.2.1  C++实现

class Solution { public: int subarraySum(vector<int>& nums, int k) { unordered_map <int,int> hash; // 统计前缀和出现的次数 // 细节 hash[0] = 1; // 用一个变量标记一下前缀和 int sum = 0,ret = 0; for(auto x : nums) { sum += x; // 计算当前位置的前缀和 if(hash.count(sum - k)) ret += hash[sum - k]; hash[sum]++; } return ret; } };
时间复杂度:O(n),空间复杂度:O(1)。

1.2.2  Java实现

// Java写法 class Solution { public int subarraySum(int[] nums, int k) { Map<Integer, Integer> hash = new HashMap<Integer, Integer>(); hash.put(0, 1); int sum = 0, ret = 0; for (int x : nums) { sum += x; // 计算当前位置的前缀和 ret += hash.getOrDefault(sum - k, 0); // 统计结果 hash.put(sum, hash.getOrDefault(sum, 0) + 1); // 把当前的前缀和丢到哈希表里面 } return ret; } }
时间复杂度:O(n),空间复杂度:O(1)。

1.3  博主手记

本题整个的思路、算法原理、解题过程博主在纸上推导了一遍,大家可以参考一下手记的推导过程!最好做题的过程中自己也推导一遍!!!自己能够推导很重要!


030  和可被K整除的子数组

本题是某一年的蓝桥杯竞赛原题哦!桀桀桀,大家做做看吧!

力扣链接:974. 和可被 K 整除的子数组

力扣题解链接:前缀和 + 哈希表解决【和可被K整除的子数组】问题

题目描述:

2.1  解法一:暴力枚举

暴力解法就是枚举出所有的子数组的和,这里不再赘述。

2.2  解法二:前缀和(余数)存在哈希表中

2.2.1  补充知识

2.2.1.1  (数学公式)同余定理

如果(a - b)% n == 0,那么我们可以得到一个结论:a % n == b % n。用文字叙述就是,如果两个数相减的差能被n整除,那么这两个数对n取模的结果相同。

例如:(26 - 2) %12 == 0,那么26 %12 == 2 % 12 == 2。
2.2.1.2  c++中负数取模的结果,以及如何修正【负数取模】的结果

(1)c++中关于负数的取模运算,结果是【把负数当成正数,取模之后的结果加上一个负号】。

例如:-1 % 3 = (-1 % 3) = -1

(2)因为有负数,为了防止发生【出现负数】的结果,以(a%n+n)%n的形式输出保证为正。

例如:-1 % 3 =(-1 % 3 + 3) % 3 = 2

2.2.2  算法思路

思路与560.和为K的子数组这道题的思路相似。

还是用这张图——

设为数组中的任意位置,用sum[ i ]表示[0,i]区间内所有元素的和。

1、想知道有多少个「以i为结尾的可被k整除的子数组」,就要找到有多少个起始位置为x1,x2,x3...使得[x,i]区间内的所有元素的和可被k整除。

2、设[0,x - 1]区间内所有元素之和等于a,[0,i]区间内所有元素的和等于,可得(b - a) % k == 0。

3、由同余定理可得,[0,x - 1]区间与[0,i]区间内的前缀和同余。于是问题就变成——

(1)找到在[0,i-1]区间内,有多少前缀和的余数等于sum[ i ] % k的即可。

我们不用真的初始化一个前缀和数组,因为我们只关心在 i 位置之前,有多少个前缀和等于sum[ i ] - k。因此,我们仅需用一个哈希表,一边求当前位置的前缀和,一边存下之前每一种前缀和出现的次数。

2.3  算法实现

2.3.1  C++实现

class Solution { public: int subarraysDivByK(vector<int>& nums, int k) { unordered_map<int,int> hash; // 统计前缀和出现的次数 // 处理细节 hash[0] = 1; // 这里存的依旧是余数,只不过0 % k还是0,所以这里存的是0这个数的余数 int sum = 0,ret = 0; for(auto x : nums) { sum += x; // 算出当前位置的前缀和 // 求出修正后的余数 int r = (sum % k + k) % k; if(hash.count(r)) ret += hash[r]; // 统计结果 hash[r]++; // 继续遍历 } return ret; } };
时间复杂度:O(n),空间复杂度:O(1)。

2.3.2  Java实现

// Java写法 class Solution { public int subarraysDivByK(int[] nums, int k) { Map<Integer, Integer> hash = new HashMap<Integer, Integer>(); hash.put(0 % k, 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; } };
时间复杂度:O(n),空间复杂度:O(1)。  

2.4  博主手记

本题整个的思路、算法原理、解题过程博主在纸上推导了一遍,大家可以参考一下手记的推导过程!最好做题的过程中自己也推导一遍!!!自己能够推导很重要!


结尾

往期回顾:

【优选算法必刷100题】第027~28题(前缀和算法):寻找数组的中心下标、除自身以外数组的乘积

结语:既然都看到这里啦!就请大佬不要忘记给博主来个“一键四连”哦!

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡

૮₍ ˶ ˊ ᴥ ˋ˶₎ა


Read more

C++ 设计模式概述及常用模式

C++ 设计模式概述 本文介绍了C++中23种设计模式的分类及实现示例,主要分为三大类: 创建型模式(5个):单例模式(常用)、工厂方法模式(常用)、抽象工厂模式(常用)、建造者模式和原型模式。这些模式专注于对象的创建机制。 结构型模式(7个):适配器模式(常用)、桥接模式、组合模式和装饰器模式(常用)等。这些模式处理类和对象的组合方式。 行为型模式:未完整列出,但包含观察者模式等(未展示完整代码)。 文章通过简洁的C++代码示例展示了常用设计模式的实现方法,如单例模式通过私有构造函数和静态方法确保唯一实例,工厂方法模式通过抽象工厂类创建产品等。这些模式为解决特定设计问题提供了可重用的解决方案。 C++ 设计模式概述及常用模式 设计模式可分为三大类:创建型、结构型、行为型。以下是23个设计模式的分类及代码示例: 一、创建型模式(5个) 1. 单例模式(Singleton)⭐ 常用 classSingleton{private:static

By Ne0inhk

STL:vector的常用接口使用及底层讲解和实现

目录 1.vector的介绍及使用 1.1 vector的介绍 1.2 vector的使用 1.2.1vector的构造 1.2.2 vector iterator 的使用 1.2.3vector的空间增长问题 1.2.3 vector 增删查改 1.2.4vector 迭代器失效问题。(重点) 2.vector深度剖析及模拟实现 2.2 动态二维数组理解 1.vector的介绍及使用 1.1 vector的介绍 vector的文档介绍:https://cplusplus.com/reference/vector/vector/ 1.2

By Ne0inhk
【数据结构】二叉搜索树 C++ 简单实现:增删查改全攻略

【数据结构】二叉搜索树 C++ 简单实现:增删查改全攻略

目录 前言: 1、什么是二叉搜索树? 2、二叉搜索树性能分析 3、key类型二叉搜索树的实现 节点结构 类结构 3.1、插入 3.2、中序遍历 3.3、查找 3.4、删除 4、key_value类型二叉搜索树的实现 节点结构 类结构 4.1、构造函数 4.1.1 默认构造 4.1.2 拷贝构造 4.2、赋值重载 4.3、析构 4.4、插入 总结 前言: 今天这篇,

By Ne0inhk

C++与Rust高效集成方案(双向绑定技术大揭秘)

第一章:C++与Rust双向绑定技术概述 在现代系统级编程中,C++ 与 Rust 的混合开发正逐渐成为构建高性能、高安全性应用的重要路径。两者各自具备独特优势:C++ 拥有成熟的生态系统和广泛的应用基础,而 Rust 则通过其所有权模型保障内存安全,避免了传统指针操作带来的隐患。双向绑定技术使得这两种语言可以在同一项目中无缝协作,实现函数互调、数据共享与异常传递。 技术背景与核心挑战 实现 C++ 与 Rust 的双向调用需克服 ABI 兼容性、内存管理策略差异以及类型系统不匹配等问题。关键在于使用 C 语言作为中间接口层,因为 C++ 和 Rust 都能良好支持与 C 的互操作。 基本交互模式 典型的绑定流程包括以下步骤: * 在 Rust 中使用 #[no_mangle] 和 extern "C"

By Ne0inhk