web-dev-qa-db-fra.com

Nombre de sous-réseaux divisibles par k

J'avais la question suivante en entrevue et, malgré le fait que j'ai donné une implémentation fonctionnelle, ce n'était pas assez efficace. 

Une tranche du tableau A est toute paire d’entiers (P, Q) tels que 0 ≤ P ≤ Q <N. Une tranche (P, Q) du tableau A est divisible par K si le nombre A [P ] + A [P + 1] + ... + A [Q − 1] + A [Q] est divisible par K.

La fonction que je devais écrire devait renvoyer le nombre de tranches divisibles par K. La complexité temporelle attendue était de O (max (N, K)) et la complexité de l'espace, de O (K).

Ma solution était la plus simple, une boucle dans une autre et vérifie chaque tranche: O (n ^ 2)

J'ai réfléchi mais je ne peux vraiment pas comprendre comment je peux le faire dans O (max (N, K)). 

Ce peut être une variante du problème de la somme des sous-ensembles }, mais je ne sais pas comment compter chaque sous-tableau. 

EDIT: Les éléments d'un tableau peuvent être des négatifs. Voici un exemple:

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}
20
antoniobg

Comme vous n'êtes intéressé que par les nombres divisibles par K, vous pouvez faire tous les calculs modulo K. Considérons le tableau de somme cumulatif S tel que S[i] = S[0] + S[1] + ... + S[i]. Alors (P, Q) est une tranche divisible par K ssi S[P] = S[Q] (rappelez-vous que nous faisons tous les calculs modulo K). Il suffit donc de compter pour chaque valeur possible de [0, ..., K-1] combien de fois elle apparaît dans S.

Voici quelques pseudocodes:

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

Une fois que vous savez qu'il s'agit de x cellules dans S ayant la valeur i, vous souhaitez compter le nombre de tranches commençant dans une cellule de valeur i et se terminant par une cellule de valeur i, ce nombre est x ( x - 1 ) / 2. Pour résoudre les problèmes de Edge, nous ajoutons une cellule de valeur 0.

Que signifie x ( x - 1 ) / 2 pour: Supposons que notre tableau est [4, 5, 0] et que la fréquence de 4 comme préfixe somme est x, ce qui est 3 dans ce cas. Nous pouvons maintenant conclure de la valeur de x qu'il existe au moins x-1 nombres qui sont soit divisibles par k, soit dont le mod k est égal à 0. Le total des sous-réseaux possibles parmi ces x-1 nombres est 1 + 2 + 3. ... + (x - 1) qui est ( ( x - 1 ) * ( ( x - 1 ) + 1 ) / 2. (Formule standard pour la somme de 1 à N, où N représente (x - 1).

27
Thomash

Pour un nombre donné X...

L'idée de base:  

the sum from the first element to b = the sum from the first element to a
                                    + the sum of the elements between the two

Alors:

the sum of the elements between the two = the sum from the first element to b
                                        - the sum from the first element to a

Ensuite, si les sommes de droite ont le même reste quand elles sont divisées par X, les restes seront annulés et la somme des éléments entre les deux sera divisible par X. Une élaboration:

C = the sum of the elements between the two
B = the sum from the first element to b
A = the sum from the first element to a

Nous pouvons maintenant convertir B au format PX + Q et A au format RX + S, pour certains entiers P, Q, R et S, avec 0 <= Q, S < X. Ici, par définition, Q et S seraient les restes respectifs de B et A divisés par X.

Ensuite nous avons:

C = (PX + Q) - (RX + S)
C = PX + Q - RX - S
C = PX - RX + Q - S
C = (P-R)X + Q - S

Clairement, (P-R)X est divisible par X (le résultat est simplement (P-R)). Maintenant nous avons juste besoin que Q - S soit divisible par X, mais, depuis 0 <= Q, S < X, ils devront être égaux.

Exemple:

Soit B = 13, A = 7, X = 3.

Ici B % X = 1 et A % X = 1.

Nous pouvons réécrire B comme 4*3 + 1 et A comme 2*3 + 1.

Puis C = 4*3 + 1 - 2*3 - 1 = 2*3, divisible par 3.

Approche de haut niveau:

Construisez une carte de hachage qui stockera la somme cumulée de tous les nombres jusqu’à présent. mod X correspond au nombre de fois où cette valeur restante apparaît (construite dans O(n)).

Augmenter la valeur de 0 de un - cela correspond au début du tableau.

Initialiser un compte à 0.

Parcourez la carte de hachage et ajoutez nC2 (= value!/(2*(value-2)!) ) au compte. Le 2 que nous choisissons ici sont les positions de début et de fin du sous-tableau.

Le compte est la valeur souhaitée.

Durée:

O(n) attendu.

Exemple:

Input:    0  5  3  8  2  1
X = 3

Sum:   0  0  5  8 16 18 19
Mod 3: 0  0  2  2  1  0  1

Map:
  0 -> 3
  2 -> 2
  1 -> 2

Count = 3! / 2*(3-2)! = 3  +
        2! / 2*(2-2)! = 1  +
        2! / 2*(2-2)! = 1
      = 5

Les sous-tableaux seront:

0  5  3  8  2  1
-                     0                 =  0 % 3 = 0
-------------         0 + 5 + 3 + 8 + 2 = 18 % 3 = 0
   ----------         5 + 3 + 8 + 2     = 18 % 3 = 0
      -               3                 =  3 % 3 = 0
            ----      2 + 1             =  3 % 3 = 0
4
JerryGoyal
    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;
    }
1
user2444873
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();


    }
1
annya

Merci pour votre solution , @damluar, c'est quand même très chouette! Je veux juste ajouter quelques commentaires. 

  1. La sortie devrait être 7, pas 6 comme votre sortie. Parce que nous avons 7 sous-réseaux qui sont divisibles par k comme ci-dessous, ajoutez res += storedArray[0]; pour le réparer.

{4, 5, 0, -2, -3, 1}; {5}; {5, 0}; {5, 0, -2, -3}; {0}; {0, -2, -3}; {-2, -3}

Lien de référence

  1. L'initialisation de cache[0]++; dépend du langage; si vous utilisez C++, vous en avez besoin, mais c'est inutile pour Java [ link ].

Code:

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; 
}
}
0
Tung Le

Voici une implémentation Java de la solution proposée par @Thomash. 

La seconde boucle n'est pas nécessaire, car nous pouvons directement augmenter la réponse de la valeur actuelle, puis l'incrémenter.

Pour éviter un index de tableau négatif, nous devons également ajuster le calcul du module.

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;
}
0
damluar

Exemple :-

Tableau d'entrée

int [] nums = {4,3,1,2,1,5,2};

K est 3

Somme consécutive

4,7,8,10,11,16,18

Divise le tableau de somme consécutif au-dessus par 3  

1,1,2,1,2,1,0

nous avons donc quatre 1, deux 2, un 0

le compte total sera donc (4 * 3)/2 + (2 * 1)/2 + (2 * 1)/2 = 8

(4 * 3)/2 vient de sélectionner deux 1 sur quatre, c’est-à-dire nC2 = n (n-1)/2

Voici le programme

public statique 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;
}

Version optimisée de ce qui précède

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;
    }
0
M Sach