力扣 523. 连续的子数组和

题目说明

  • 给你一个整数数组 nums 和一个整数 k ,编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:

    • 子数组大小 至少为 2 ,且
    • 子数组元素总和为 k 的倍数。

    如果存在,返回 true ;否则,返回 false

    如果存在一个整数 n ,令整数 x 符合 x = n * k ,则称 xk 的一个倍数。

  • 提示:

    • 1 <= nums.length <= 10^5
    • 0 <= nums[i] <= 10^9
    • 0 <= sum(nums[i]) <= 2^31 - 1
    • 1 <= k <= 2^31 - 1

示例

示例 1:

1
2
3
输入:nums = [23,2,4,6,7], k = 6
输出:true
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6 。

示例 2:

1
2
3
4
输入:nums = [23,2,6,4,7], k = 6
输出:true
解释:[23, 2, 6, 4, 7] 是大小为 5 的子数组,并且和为 42 。
42 是 6 的倍数,因为 42 = 7 * 6 且 7 是一个整数。

示例 3:

1
2
输入:nums = [23,2,6,4,7], k = 13
输出:false

笔者理解

此题是一道动态规划算法问题,在力扣题库中被定义为中等题。

解法

当笔者阅读完此题后,一开始打算通过前缀和直接暴力试一试的,但是后来发现题目是1e5级别的,暴力肯定过不了,后来发现,当两个数对k取余相同时,这两个数的差值等于k的倍数,这里我们可以运用这个余数定理来判断两个前缀和之间的区间是不是符合题意,让我们来看看具体如何实现的吧。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Solution {
/*
* 前缀和
* 同余原理
*/
public boolean checkSubarraySum(int[] nums, int k) {
int n = nums.length;

// 不存在长度为2的子数组
if (n < 2) {
return false;
}

// 计算前i个数的和
int sum = 0;
// 存储各i个数的和对k取余的结果
HashSet<Integer> set = new HashSet<>();

for (int i = 0; i < n; i++) {
sum = sum + nums[i];

// 如果存在取余相等,代表前一个i个数的和 与 当前前i个数的和 的差值
// 也就是sum[i2] - sum[i1]等于k的倍数
// 此时i1到i2区间就符合要求
if (set.contains(sum % k)) {
return true;
}

// 从0开始,第一次就在在set中存入一个0,
// 之后存在sum[i]对k取余等于0就返回true
// 实际上每次存入的是sum[i - 1] % k
set.add((sum - nums[i]) % k);
}

return false;
}
}

时间空间效率都还行,可见此解法还比较适合此题;

image.png

总结

本题是今天的每日一题,难度是为中等,感兴趣的朋友都可以去尝试一下,此题还有其他更多的解法,朋友们可以自己逐一尝试。