当前位置: 首页 > news >正文

蓝桥杯真题——k倍区间

目录

题目链接:2.k倍区间 - 蓝桥云课

题目描述

输入描述

输出描述

输入输出样例

示例

运行限制

解法一:暴力(一点分是一点分)

思路:

具体流程:

优缺点:

Java写法:

C++写法:

AC情况

时间复杂度和空间复杂度

解法二:前缀和+哈希表优化

优化思路:

算法实现:

代码解释:

Java写法:

C++写法:

这行代码:modCount.put(0L, 1L);

1. 前缀和的含义:

2. 初始化模值为 0:

3. 实际含义:

4. 避免漏掉边界情况:

运行时间

时间复杂度和空间复杂度

总结

1. 题目描述

2. 暴力解法

3. 优化思路:前缀和 + 哈希表

前缀和的思想:

使用哈希表:

关键点:


题目链接:2.k倍区间 - 蓝桥云课

        注:下述题目描述和示例均来自蓝桥云客

题目描述

        给定一个长度为 NN 的数列,A1,A2,⋯ANA1​,A2​,⋯AN​,如果其中一段连续的子序列 Ai,Ai+1,⋯AjAi​,Ai​+1,⋯Aj​ ( i≤ji≤j ) 之和是 KK 的倍数,我们就称这个区间 [i,j][i,j] 是 K 倍区间。

        你能求出数列中总共有多少个 KK 倍区间吗?

输入描述

        第一行包含两个整数 NN 和 KK( 1≤N,K≤1051≤N,K≤105 )。

        以下 N 行每行包含一个整数 AiAi​ ( 1≤Ai≤1051≤Ai​≤105 )

输出描述

        输出一个整数,代表 K 倍区间的数目。

输入输出样例

示例

输入

5 2
1
2
3
4
5

输出

6

运行限制

  • 最大运行时间:2s
  • 最大运行内存: 256M

解法一:暴力(一点分是一点分)

思路:

  • 暴力解法:通过两层嵌套循环,遍历所有可能的子数组区间 A[i]A[j],逐个计算每个子数组的和,然后判断是否为 K 的倍数。
  • 外层循环用于选择子数组的起点 i,内层循环用于遍历从 i 开始的所有子数组并计算其和 total,判断 total % K == 0 是否成立。

具体流程:

  1. 输入处理:首先从输入中读取数列的长度 N 和倍数 K,然后将数列的元素存储在数组 nums 中。
  2. 两层循环
    • 外层循环遍历每一个起点 i
    • 内层循环从 i 开始,向后遍历,累加当前区间的和 total
    • 每次计算和 total 后,判断其是否为 K 的倍数,如果是,则增加结果 res
  3. 输出结果:输出符合条件的子数组的数量 res

优缺点:

  • 时间复杂度:暴力解法的时间复杂度为 O(N^2),因为外层循环遍历 N 次,每次内层循环最多遍历 N 次,导致总时间复杂度为 O(N^2)。当 N 最大为 10^5 时,这种复杂度不可接受,可能导致超时。不巧,这里的N最大还真是10^5

  • 空间复杂度:空间复杂度为 O(N),因为只使用了一个大小为 N 的数组来存储输入数据。

Java写法:

import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);long n = sc.nextLong();long k = sc.nextLong();long[] nums = new long[(int)n];for(int i = 0; i < n; i++){// 将所有的整数都存储到数组之中nums[i] = sc.nextLong();}long res = 0;for(int i = 0; i < n; i++){long total = 0;for(int j = i; j >= 0; j--){if((total += nums[j]) % k == 0){// System.out.println(nums[i] + ":" + total);res++;}}}System.out.print(res);sc.close();}
}

C++写法:

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;int main() {// 输入 N 和 Klong long n, k;cin >> n >> k;// 存储输入的数列vector<long long> nums(n);for (int i = 0; i < n; i++) {cin >> nums[i];  // 输入每个数字}long long res = 0;  // 结果计数器// 遍历所有的子数组区间for (int i = 0; i < n; i++) {long long total = 0;  // 当前子数组的和for (int j = i; j >= 0; j--) {total += nums[j];  // 计算从 i 到 j 的子数组和if (total % k == 0) {  // 如果当前和是 K 的倍数res++;  // 增加结果计数器}}}cout << res << endl;  // 输出结果return 0;
}

AC情况

时间复杂度和空间复杂度

  • 时间复杂度O(n^2),遍历一次数组,每次操作都在常数时间内完成。
  • 空间复杂度O(K),哈希表最多存储 K 个不同的模值。



解法二:前缀和+哈希表优化

        要优化这个问题,可以利用 前缀和哈希表 来减少时间复杂度,从 O(N^2) 降到 O(N)。具体思路如下:

优化思路:

  1. 前缀和:用一个数组 prefix_sum 存储从数组起始到当前位置的累加和。对于任意两个位置 iji <= j),子数组的和可以通过前缀和的差来计算:

sum(A[i],A[i+1],...,A[j])=prefix\_sum[j]-prefix\_sum[i-1]

  1. 如果 prefix_sum[j] - prefix_sum[i-1]K 的倍数,那么 prefix_sum[j] % K == prefix_sum[i-1] % K(也就是说倍数减倍数也就是倍数少了几倍而已还是倍数)

  2. 哈希表:通过哈希表记录每个前缀和的模 K 的出现次数。当计算到位置 j 时,检查当前 prefix_sum[j] % K 是否在哈希表中出现过。如果出现过,说明有一些区间的和是 K 的倍数。


算法实现:

  • 使用一个哈希表 mod_count 来记录每个前缀和对 K 取模的结果出现的次数。
  • 遍历数组时,计算当前的前缀和并取模,判断是否之前出现过相同的模值,若出现过,说明存在一个或多个区间的和是 K 的倍数。
  • 对于每个 prefix_sum[j] % K,如果它在哈希表中已经存在,说明之前的某些前缀和与当前位置的前缀和之间的区间和是 K 的倍数。

代码解释:

  1. 输入:首先输入 nk,然后读取数组 nums
  2. 初始化
    • prefix_sum 用来存储当前的前缀和。
    • mod_count 哈希表用于存储每个前缀和对 k 取模的结果及其出现次数。初始时 mod_count[0] = 1,因为一个前缀和为 0 的区间本身就符合条件。
  3. 遍历
    • 在遍历每个元素时,更新 prefix_sum,然后计算当前 prefix_sum % k
    • 如果该模值已经出现在哈希表中,说明存在从某个位置到当前的位置的子数组和是 k 的倍数。每次找到一个符合条件的模值,结果计数器 res 增加。
    • 最后更新哈希表中该模值的计数。
  4. 输出:遍历结束后输出结果。

Java写法:

import java.util.*;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);// 输入 N 和 Klong n = sc.nextLong();long k = sc.nextLong();// 存储输入的数列long[] nums = new long[(int) n];// 输入每个数字for (int i = 0; i < n; i++) {nums[i] = sc.nextLong();  }// 结果计数器long res = 0;  // 当前前缀和long prefixSum = 0;// 哈希表,用于存储 prefixSum % K 的计数  Map<Long, Long> modCount = new HashMap<>();  // 初始条件:前缀和为 0 时的模为 0,表示从数组开始到当前的子数组和为 K 的倍数modCount.put(0L, 1L);// 遍历数列for (int i = 0; i < n; i++) {// 更新前缀和prefixSum += nums[i];  // 计算当前前缀和对 K 的模long mod = prefixSum % k;// 如果当前 mod 在哈希表中出现过,则说明存在前缀和差为 K 的倍数的子数组if (modCount.containsKey(mod)) {// 增加结果计数,将其+1res += modCount.get(mod);  }// 更新当前 mod 的计数,将其+1modCount.put(mod, modCount.getOrDefault(mod, 0L) + 1);}// 输出结果System.out.println(res);  sc.close();}
}

C++写法:

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;int main() {// 输入 N 和 Klong long n, k;cin >> n >> k;// 存储输入的数列vector<long long> nums(n);for (int i = 0; i < n; i++) {cin >> nums[i];  // 输入每个数字}long long res = 0;  // 结果计数器long long prefix_sum = 0;  // 当前前缀和unordered_map<long long, long long> mod_count;  // 哈希表,用于存储 prefix_sum % K 的计数// 初始条件:前缀和为 0 时的模为 0,表示从数组开始到当前的子数组和为 K 的倍数mod_count[0] = 1;// 遍历数列for (int i = 0; i < n; i++) {prefix_sum += nums[i];  // 更新前缀和// 计算当前前缀和对 K 的模long long mod = prefix_sum % k;// 如果 mod 为负数,调整为非负数(在 C++ 中,负数取模会得到负数)if (mod < 0) mod += k;// 如果当前 mod 在哈希表中出现过,则说明存在前缀和差为 K 的倍数的子数组if (mod_count.find(mod) != mod_count.end()) {res += mod_count[mod];  // 增加结果计数}// 更新当前 mod 的计数mod_count[mod]++;}cout << res << endl;  // 输出结果return 0;
}

这行代码:modCount.put(0L, 1L);

modCount.put(0L, 1L);

        作用是在哈希表中初始化前缀和为 0 的情况,它表示从数组的开始位置到当前位置之间的子数组和恰好是 K 的倍数。为了理解为什么这么设置,可以从以下几个角度来解释:

1. 前缀和的含义

        前缀和 prefixSum[i] 是从数组起始位置到第 i 个元素的和。假设数组 nums[A1, A2, ..., An],那么前缀和是:

prefixSum[i] = A1 + A2 + ... + Ai

        如果一个子数组 A[i, j] 的和是 K 的倍数,那么 prefixSum[j] - prefixSum[i-1] 必须是 K 的倍数。也就是说:

↓↓↓↓这里很重要↓↓↓↓

(prefixSum[j] - prefixSum[i-1]) \% K == 0

这等价于:

prefixSum[j]\% K == prefixSum[i-1]\% K

2. 初始化模值为 0

        假设我们从数组的开始位置就能够找到一个子数组和为 K 的倍数。为了方便这个情况的处理,我们需要在哈希表 modCount 中提前设置 modCount[0] = 1,表示前缀和为 0 时的模已经出现过一次。

        为什么要设置为 1 呢?这表明如果我们在遍历过程中遇到一个前缀和的模值为 0,它意味着从数组的起始位置到当前位置之间的子数组和正好是 K 的倍数。这相当于一个“空的”子数组(从数组的开始位置到当前位置)的和是 K 的倍数。

3. 实际含义

  • 当遍历数组时,prefixSum[i] 表示当前数组从起始位置到第 i 个位置的元素和。
  • 如果在某个位置 iprefixSum[i] % K == 0,意味着从数组的第一个元素到当前元素的和就是 K 的倍数。因此,我们需要提前设定 modCount[0] = 1,表示从数组开始到当前子数组的和是 K 的倍数。

4. 避免漏掉边界情况

        例如,假设 nums = [5, 10, 15]K = 5,此时从第一个元素到第三个元素的和 5 + 10 + 15 = 305 的倍数。如果没有 modCount[0] = 1 作为初始条件,我们可能会漏掉这个情况。


运行时间

时间复杂度和空间复杂度

  • 时间复杂度O(N),遍历一次数组,每次操作都在常数时间内完成。
  • 空间复杂度O(K),哈希表最多存储 K 个不同的模值。


总结

        我们讲解了首先使用暴力的算法可以的一点分,但是对于N^5的数来说O(N^2)就无法通过全部的用例了,接下来介绍了如何通过使用前缀和哈希表优化计算子数组和为 K 的倍数的个数问题,最终将时间复杂度从暴力解法的 O(N^2) 降到 O(N),使得算法能够高效地处理大规模数据。

1. 题目描述

        给定一个长度为 N 的数列,要求统计其中有多少个连续子数组,其和是 K 的倍数。数列和 K 的值都可能较大,因此不能采用暴力枚举的方式来解决。

2. 暴力解法

        暴力解法的思路是:通过两层嵌套循环,遍历所有可能的子数组区间,计算每个子数组的和,并判断其是否为 K 的倍数。这种方式的时间复杂度为 O(N^2),对于 N 最大可达 10^5 的情况,效率低下,不适用于大规模数据。

3. 优化思路:前缀和 + 哈希表

        为了将暴力解法优化到 O(N),可以利用前缀和哈希表的组合。

前缀和的思想:

        前缀和是指数组中从第 1 个元素到第 i 个元素的累加和。对于任意两个位置 ijprefixSum[j] - prefixSum[i-1] 即为 A[i]A[j] 的子数组和。如果这个差值是 K 的倍数,那么 prefixSum[j] % K == prefixSum[i-1] % K

使用哈希表:

        我们通过一个哈希表 mod_count 来记录每个前缀和对 K 取模的结果出现的次数。每次计算新的前缀和时,查看是否存在相同的模值,若存在,说明从某个位置到当前的位置之间的子数组和是 K 的倍数。

关键点:

咱就是说:

↓↓↓↓这里很重要↓↓↓↓

(prefixSum[j] - prefixSum[i-1]) \% K == 0

这等价于:

prefixSum[j]\% K == prefixSum[i-1]\% K


http://www.mrgr.cn/news/74735.html

相关文章:

  • 小程序服务商常见问题
  • 【NLP优化】Ubuntu 20.04 下 源码安装 CasADi + Ipopt / acados
  • react的创建与书写
  • 【JavaScript】
  • vue内置方法总结
  • 修改msyql用户密码及更新mysql密码策略
  • 【性能优化】图片性能优化方案
  • Python 绘图工具详解:使用 Matplotlib、Seaborn 和 Pyecharts 绘制散点图
  • 基于Springboot+微信小程序的付费选座自习室小程序 (含源码数据库)
  • JavaScript 对象
  • fpga开发-存储器及其应用
  • 图像识别
  • AI开发-三方库-PyTorch-Matplotlib
  • TLP2361光耦器:为高速、高可靠性数字接口提供解决方案
  • STM32F407简单驱动步进电机(标准库)
  • 3.5MachineLearing1Chapter
  • 威联通Docker Compose搭建NAS媒体库资源工具NAS Tools
  • 基于51单片机的高压锅控制系统proteus仿真
  • 污水处理领域的可视化大屏,3D流程图绝对有很大用武之地。
  • PHP“well”运动健身APP 87702-计算机毕业设计项目选题推荐(附源码)
  • DAY112代码审计PHP开发框架POP链利用Yii反序列化POP利用链
  • NocoBase 本周更新汇总:提升工作流易用性
  • C/C++精品项目之图床共享云存储(3):网络缓冲区类和main
  • 「媒体邀约」科技类企业如何利用媒体专访提升品牌知名度
  • Vuex vs Pinia:新一代Vue状态管理方案对比
  • IDEA2024:右下角显示内存