emmm,又是翻车的一天,以后做题要好好分析复杂度了,今天又超时了。。。
思路
前缀和好理解,之前已经说过很多次了,这次的重点是同余定理
假设存在满足条件的区间
[i,j]
,即(s[j]-s[i]) %k==0
,也就意味着(s[j]-s[i-1]) \ k=n(n是整数)
,设a=s[j]%k,x=s[j] \ k,b=s[i-1]%k,y=s[i-1]\k
,则s[j] = x*k + a,s[i-1] = y*k + b
,如果此时a=b
,意味着s[j]-s[i-1]= x*k + a-(y*k + b)=(x-y)*k,其中x-y是整数
。
因此只要找到满足余数相同的s[j]和s[i-1]
即可,其中j>=i+1
。
因此得到以下算法步骤:
- 先计算前缀和;
- 因为
j>=i+1
,时间复杂度又要求是线性的,所以借助Hashset辅助寻找满足条件的区间:每次枚举最右侧的值,如果set中存在和它相同余数的元素的话,则返回true。
代码
超时代码,因为数组的长度是10的5次方,所以时间复杂度只能是线性的 O ( n ) O(n) O(n),而下面使用前缀和的方法,因为嵌套循环,时间复杂度达到 O ( n 2 ) O(n^2) O(n2),所以会超时。
//超时代码
public boolean checkSubarraySum(int[] nums, int k) {
int len = nums.length;
System.out.println(len);
if (len<2)
return false;
//前缀和
int [] s = new int[len+1];
for (int i=1;i<=len;++i)
s[i] = s[i-1]+nums[i-1];
//因为子数组的长度至少为2
for(int i=2;i<=len;++i)
{
for (int j=0;j<i-1;++j)
{
System.out.println((s[j]-s[i])%k);
if ((s[j]-s[i])%k == 0)
return true;
}
}
return false;
}
根据官方题解,使用同余定理,代码如下:
class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
int len = nums.length;
if (len<2)
return false;
//前缀和
int [] s = new int[len+1];
for (int i=1;i<=len;++i)
s[i] = s[i-1]+nums[i-1];
Set<Integer> set = new HashSet<>();
for(int i=2;i<=len;++i)
{
set.add(s[i-2]%k);
if (set.contains(s[i]%k))
return true;
}
return false;
}
}