给你一个整数数组
nums
和一个整数k
,编写一个函数来判断该数组是否含有同时满足下述条件的连续子数组:子数组大小 至少为
2
,且 子数组元素总和为k
的倍数。 如果存在,返回true
;否则,返回false
。如果存在一个整数
n
,令整数x
符合x = n * k
,则称x
是k
的一个倍数。
示例 1:
输入:
nums = [23,2,4,6,7]
,k = 6
输出:true
解释:[2,4]
是一个大小为2
的子数组,并且和为6
。
示例 2:
输入:
nums = [23,2,6,4,7]
,k = 6
输出:true
解释:[23, 2, 6, 4, 7]
是大小为5
的子数组,并且和为42
。
42
是6
的倍数,因为42 = 7 * 6
且7
是一个整数。
示例 3:
输入:
nums = [23,2,6,4,7]
,k = 13
输出:false
提示:
1 <= nums.length <= 10^5
0 <= nums[i] <= 10^9
0 <= sum(nums[i]) <= 2^31 - 1
1 <= k <= 231 - 1
题目地址:地址
本题涉及到一个算法:前缀和
何为前缀和
?
前缀和是为了快速求解类似于此类需要做 连续加法 的题而使用的数学工具。简单来说,就是一个新的、长度和原数组相同的数组 sum
,sum
的第 i
项是原数组 从 0
~ i
的和。
举个例子
例:需要求得 [a, b]
区间范围的和,则可以用 sum[b] - sum[a - 1]
求得。如下:
origin = [1, 2, 3, 4, 5]
sum = [1, 3, 6, 10, 15]
如果我要快速求得 origin
下标是 [2, 4]
之间的值的和,那我可以直接用 sum
数组的 sum[4] - sum[1] = 15 - 3 = 12
获得
需要注意的是
0
时,注意不要越界(可以在初始化 sum
时,就预留第一位为 0
来避免越界,当然具体求解 [a, b]
区间时,也会变为 sum[b + 1] - sum[a]
)new
出这个 sum
数组,而是用一个变量来累计循环数组的每项和而计算。就像今天的这道题一样。具体如何求解还是需要看题目的需求如果需要做一个经典的题来练习这个算法,可以参考这个:560. 和为K的子数组
我们继续使用上面分析使用的各项定义来具体分析此题
以 示例一 为例: nums = [23, 2, 4, 6, 7]
, k = 6
为了符合题目要求: 令整数 x
符合 x = n * k
,那么对于 sum
必然存在 (sum[b] - sum[a - 1]) % k === 0
。
而 sum[b]
、 sum[a - 1]
和 k
之间有什么关系呢:
sum[b] % k === sum[a - 1] % k
因为如果 [a, b]
之间存在一个整数 x
,使得满足 x = n * k
,那么必然在数组 nums[a - 1]
与 nums[b]
之间的 和 可以被 k
整除,那也就是说 sum[a - 1]
与 sum[b]
的差值,是符合条件的子集在被 k
取余时,导致余数不变,那么我们根据这个特点即可得出是否有子集满足此条件
因此我们需要创建一个数组,用来存储每次得到的余数,判断是否存在余数相同的情况
我们分析 示例一 的数据,如下:
23 % 6 = 5
(5 + 2) % 6 = 1
(1 + 4) % 6 = 5
,此时我们发现此段余数与第一次的 余数相同,则代表着 [1, 2]
区间的值的和满足了条件我们在观察上述过程,忽略了一个问题,就是假如 nums
中某值本身就符合条件,例如 示例二 的 nums = [23, 2, 6, 4, 7]
, k = 6
,如果以上述方法计算,即:
23 % 6 = 5
(5 + 2) % 6 = 1
(1 + 6) % 6 = 1
时发现,在第三步就已经求得出余数相同的值了,而此时满足条件的下标为 [3, 3]
,实际上却不满足题目要求的另一个条件: 子数组大小 至少为 2
。因此我们需要将每次取余后的值的 下标 记录并获取,然后判断 前一个取余后值得下标
与 当前值下标
之间是否 >= 2
。所以我们可以使用 哈希表
来存储此对应关系。
function checkSubarraySum(nums: number[], k: number): boolean {
if (nums.length < 2) return false
const map = new Map()
map.set(0, -1)
const length = nums.length
let remain = 0
for (let i = 0; i < length; i++) {
remain = (remain + nums[i]) % k
if (map.has(remain)) {
const prevIndex = map.get(remain)
if (i - prevIndex >= 2) return true
} else {
map.set(remain, i)
}
}
return false
};
TS AC,执行用时:128ms
, 在所有 TypeScript
提交中击败了 33.33%
的用户
TS AC中击败 100% 用户的提交,目前执行下来用时 180ms
,估计力扣增加了测试用例导致更简单的算法耗时比之前提交的复杂的算法耗时要久,这对后期用户提交并不友好
本题用到了一位数组的前缀和,那么自然也有二维数组前缀和
可以参考此题:304. 二维区域和检索 - 矩阵不可变
各大公司对前缀和的考察还是比较多的
第一次看这道题一下反应出这是前缀和的题,但不是特别理解题目如何去解决。
跟着感觉写题解的时候,其实不是特别理解前缀和与余数在本题的作用,也不太理解照葫芦画瓢写的代码,还煞有其势地写了题解,发现越写越【顺】,以为就是这样去解题。所以第一次发布后,再看自己题解时发现了问题,竟然算错余数了 = =!
之后我再次重新勾勾画画后,才理解了前缀和与余数之间的微妙的关系,我愿称之为微妙,是因为这个解题法就跟脑筋急转弯一样,必须把示例中的值逐个计算,才能发现之间的特殊,很像小学数学奥赛题,写一写才能发现其特殊,然后才会有思路
所以以后还是不要偷懒在脑海中计算了,还是拿出纸画一画吧。。。
如果我在哪里写的有问题,欢迎指出,共同进步,谢谢阅读~