题目描述
给定一个包含 非负数 的数组和一个目标 整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,且总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数。
示例 1:
输入:[23,2,4,6,7], k = 6
输出:True
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6。
示例 2:
输入:[23,2,6,4,7], k = 6
输出:True
解释:[23,2,6,4,7]是大小为 5 的子数组,并且和为 42。
官方解法:(解法链接)
方法 3: 使用 HashMap [Accepted]
算法
在这种方法中,我们使用 HashMap 来保存到第 ii 个元素为止的累积和,但我们对这个前缀和除以 kk 取余数。原因如下:
我们遍历一遍给定的数组,记录到当前位置为止的 sum%ksum 。一旦我们找到新的 sum%ksum 的值(即在 HashMap 中没有这个值),我们就往 HashMap 中插入一条记录 (sum%k, i)(sum 。
现在,假设第 ii 个位置的 sum%ksum 的值为 remrem 。如果以 ii 为左端点的任何子数组的和是 kk 的倍数,比方说这个位置为 jj ,那么 HashMap 中第 jj 个元素保存的值为 (rem + nk)%k(rem+n∗k)%k ,其中 nn 是某个大于 0 的整数。我们会发现 (rem + nk)%k = rem(rem+n∗k)%k=rem ,也就是跟第 ii 个元素保存到 HashMap 中的值相同。
基于这一观察,我们得出结论:无论何时,只要 sum%ksum 的值已经被放入 HashMap 中了,代表着有两个索引 ii 和 jj ,它们之间元素的和是 kk 的整数倍。因此,只要 HashMap 中有相同的 sum%ksum%k ,我们就可以直接返回 \teat{True} 。
public class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
int sum = 0;
HashMap < Integer, Integer > map = new HashMap < > ();
map.put(0, -1);
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
if (k != 0)
sum = sum % k;
if (map.containsKey(sum)) {
if (i - map.get(sum) > 1)
return true;
} else
map.put(sum, i);
}
return false;
}
}
解读:
在官方的三种解法中,这个解法是时间复杂度最低的算法,当然遇到此类题型,一定要考虑使用前缀和以及map来思考。
作者的思路也很简单,在前缀和的基础上做出了一点改进,我们遍历数组,并每次将前缀和求余之后再存入map(如果题目要求得出子数组的起始位置和结束位置的话,我们map存入的键后面跟上遍历坐标i就行)。为什么要这么做呢,我们首先来看一个例子。
- arr = {3,4,3,4,5} k = 11;
- 定义p[i]表示arr前i项和并求余
- p[1] = 3; /*3
- p[2] = 7; /*3,4
- p[3] = 10; /*3,4,3
- p[4] = 3; /*3,4,3,4
此时我们发现p中已经包含来余数3,这样就可以知道数组中有子数组的和为k的n倍,因为当a数对于b数求余等于c,d数再对b数求余仍然得到了c,这就说明a数一定是加上了一个能被b整除的数,即这个数就是n倍的k,同理也就说明存在子数组的和为n倍的k,这样就得到了题目的求解。