可被 k 整除的子数组数
Posted
技术标签:
【中文标题】可被 k 整除的子数组数【英文标题】:Number of subarrays divisible by k 【发布时间】:2013-05-12 10:36:02 【问题描述】:我在一次采访中遇到了以下问题,尽管我给出了一个可行的实现,但它的效率还不够高。
数组 A 的切片是任意一对整数 (P, Q),满足 0 ≤ P ≤ Q
要求我编写的函数必须返回可被 K 整除的切片数。预期时间复杂度为 O(max(N, K)),空间复杂度为 O(K)。
我的解决方案是最简单的,一个循环在另一个循环中检查每个切片:O(n^2)
我一直在想,但我真的不知道如何在 O(max(N, K)) 中做到这一点。
它可能是subset sum problem 的变体,但我不知道如何计算每个子数组。
编辑:数组中的元素可能是负数。这是一个例子:
A = 4, 5, 0, -2, -3, 1, K = 5
Function must return 7, because there are 7 subarrays which sums are divisible by 5
4, 5, 0, -2, -3, 1
5
5, 0
5, 0, -2, -3
0
0, -2, -3
-2, -3
【问题讨论】:
【参考方案1】:由于您只对可被 K 整除的数字感兴趣,因此您可以以 K 为模进行所有计算。
考虑累积和数组 S 使得S[i] = S[0] + S[1] + ... + S[i]
。那么 (P, Q) 是一个可以被 K 整除的切片,当且仅当 S[P] = S[Q]
(请记住,我们进行所有模 K 的计算)。因此,您只需计算 [0 ,..., K-1] 的每个可能值它在 S 中出现的次数。
这是一些伪代码:
B = new array( K )
B[0]++
s = 0
for i = 0 to N - 1
s = ( s + A[i] ) % K
B[s]++
ans = 0
for i = 0 to K - 1
ans = ans + B[i] * ( B[i] - 1 ) / 2
一旦您知道它们是 S 中具有值 i 的 x 个单元格,您想要计算从具有值 i 的单元格开始到具有值 i 的单元格结束的切片数,这个数字是 x ( x - 1 ) / 2
。为了解决边缘问题,我们添加一个值为 0 的单元格。
x ( x - 1 ) / 2
代表什么:假设我们的数组是 [4, 5, 0] 并且 4 作为前缀和的频率是 x,在这种情况下是 3。现在我们可以从 x 的值得出结论,至少有 x-1 个数字可以被 k 整除或 mod k 等于 0。现在这些 x-1 数字中可能的子数组总数为 1 + 2 + 3 ... + ( x - 1 ) 即 ( ( x - 1 ) * ( ( x - 1 ) + 1 ) / 2
。 (从 1 到 N 求和的标准公式,其中 N 代表 ( x - 1 )。
【讨论】:
它没有改变任何东西,我的解决方案仍然有效。 B[0]++和B[S]++是什么意思? @Thomash 从理论上讲,模数仅用于正数。对于负数,模没有定义的值。如果您声称您的解决方案适用于负数,则应定义模数如何适用于负数。B[0]++
是干什么用的?
ans = ans + B[i] * ( B[i] - 1 ) / 2
我想为其他不理解的人澄清这一点:B 包含每个索引 mod 的总和 mod K 的数量。由于我们试图找到 sum mod K = 0 中的差异数,因此实现这一目标的唯一方法是减去数组中具有相同 mod 的两个和。这一行通过在 B 的每个 mod 中找到 2 组合的数量来实现这一点。 n 选择 2 = n!/(2!(n-2)!) = n(n-1)/2【参考方案2】:
这是@Thomash 提出的解决方案的Java 实现。
第二个循环不是必须的,因为我们可以直接将答案增加当前值,然后增加它。
为了避免负数组索引,我们还必须调整模块计算。
public static int countSubarrays(int[] nums, int k)
int[] cache = new int[k];
cache[0]++;
int s = 0, counter = 0;
for (int i = 0; i < nums.length; i++)
s = ((s + nums[i]) % k + k) % k;
counter += cache[s];
cache[s]++;
return counter;
【讨论】:
【参考方案3】:示例:-
输入数组
int [] nums = 4,3,1,2,1,5,2;
K 是 3
连续求和
4,7,8,10,11,16,18
将上面的连续和数组除以3
1,1,2,1,2,1,0
所以我们有四个 1,两个 2,一个 0
所以总数将是 (4*3)/2 + (2*1)/2 + (2*1)/2 = 8
(4*3)/2 来自从四个中选择任意两个 1,即 nC2 = n(n-1)/2
这是程序
public static long countSubArrayDivByK(int k, int[] nums)
Map<Integer, Integer> modulusCountMap = new HashMap<Integer, Integer>();
int [] consecSum = new int[nums.length];
consecSum[0]=nums[0];
for(int i=1;i<nums.length;i++)
consecSum[i]= consecSum[i-1] +nums[i];
for(int i=0;i<nums.length;i++)
consecSum[i]= consecSum[i]%k;
if(consecSum[i]==0 && modulusCountMap.get(consecSum[i])==null)
modulusCountMap.put(consecSum[i], 2);
else
modulusCountMap.put(consecSum[i], modulusCountMap.get(consecSum[i])==null ? 1 : modulusCountMap.get(consecSum[i])+1);
int count = 0;
for (Integer val : modulusCountMap.values())
count = count + (val*(val-1))/2;
return count;
上述优化版
static long customOptimizedCountSubArrayDivByK(int k, int[] nums)
Map<Integer, Integer> modulusCountMap = new HashMap<Integer, Integer>();
int [] quotient = new int[nums.length];
quotient[0]=nums[0]%3;
if(quotient[0]==0)
modulusCountMap.put(quotient[0], 2);
else
modulusCountMap.put(quotient[0], 1);
for(int i=1;i<nums.length;i++)
quotient[i]= (quotient[i-1] + nums[i])%3;
if(quotient[i]==0 && modulusCountMap.get(quotient[i])==null)
modulusCountMap.put(quotient[i], 2);
else
modulusCountMap.put(quotient[i], modulusCountMap.get(quotient[i])==null ? 1 : modulusCountMap.get(quotient[i])+1);
int count = 0;
for (Integer val : modulusCountMap.values())
count = count + (val*(val-1))/2;
return count;
【讨论】:
【参考方案4】: private int GetSubArraysCount(int[] A, int K)
int N = A.Length;
int[] B = new int[K];
for (int i = 0; i < B.Length; i++)
B[i] = 0;
B[0]++;
int s = 0;
for (int i = 0; i < N; i++)
s = (s + A[i]) % K;
while (s < 0)
s += K;
B[s]++;
int ans = 0;
for (int i = 0; i <= K - 1; i++)
ans += B[i] * (B[i] - 1) / 2;
return ans;
【讨论】:
【参考方案5】:感谢your solution、@damluar,不过它非常简洁!我只想添加一些cmets。
-
输出应该是 7,而不是 6 作为您的输出。因为我们有 7 个可以被 k 整除的子数组,如下所示,添加
res += storedArray[0];
来修复它。
4, 5, 0, -2, -3, 1; 5; 5, 0; 5, 0, -2, -3; 0; 0, -2, -3; -2, -3
Ref link
-
初始化
cache[0]++;
取决于语言,如果使用 C++,则需要,但对于 java [link] 则不需要。
代码:
public class HelloWorld
public static void main(String []args)
int [] A = new int[] 4,5,0,-2,-3,1;
int k = 5;
int ans=0;
System.out.println(countSubArray(A, k)); // output = 7
public static int countSubArray(int [] nums, int k)
int [] storedArray = new int[k];
int sum=0, res=0;
for(int i=0; i<nums.length; i++)
sum = (((sum + nums[i]) % k) + k) % k;
res += storedArray[sum];
storedArray[sum]++;
res += storedArray[0];
return res;
【讨论】:
【参考方案6】:static void Main(string[] args)
int[] A = new int[] 4, 5, 0, -2, -3, 1 ;
int sum = 0;
int i, j;
int count = 0;
for (i = 0; i < A.Length; i++)
for (j = 0; j < A.Length; j++)
if (j + i < 6)
sum += A[j + i];
if ((sum % 5) == 0)
count++;
sum = 0;
Console.WriteLine(count);
Console.ReadLine();
【讨论】:
建议:您在内部循环中遍历整个数组,而实际上您只需要遍历其中的一部分——因为j + i
将在中途达到 6。此外,即使在该迭代中没有设置它,您也正在使用 sum
进行比较。这个解决方案还需要一些工作。
OP 要求一种有效的方法,这是一个蛮力解决方案【参考方案7】:
public class SubArrayDivisible
public static void main(String[] args)
int[] A = 4, 5, 0, -2, -3, 1;
SubArrayDivisible obj = new SubArrayDivisible();
obj.getSubArrays(A,5);
private void getSubArrays(int[] A,int K)
int count = 0,s=0;
for(int i=0;i<A.length;i++)
s = 0;
for(int j = i;j<A.length;j++)
s = s+A[j];
if((s%K) == 0)
System.out.println("Value of S "+s);
count++;
System.out.println("Num of Sub-Array "+count);
【讨论】:
这是我在采访中给出的解决方案,但它的复杂度是 O(n!),他们要求 O(max(k,n))。托马斯的解决方案是正确的以上是关于可被 k 整除的子数组数的主要内容,如果未能解决你的问题,请参考以下文章
[LeetCode] 974. 和可被 K 整除的子数组 !!!