【问题标题】:Number of subarrays divisible by k可被 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}

【问题讨论】:

    标签: arrays algorithm subset-sum


    【解决方案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。

          1. 输出应该是 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

          1. 初始化 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))。托马斯的解决方案是正确的
            猜你喜欢
            • 1970-01-01
            • 2012-07-31
            • 2012-10-16
            • 1970-01-01
            • 1970-01-01
            • 2015-12-22
            • 2020-06-26
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多