web-dev-qa-db-ja.com

kで割り切れるサブ配列の数

私はインタビューで次の質問をしました、そして私が実用的な実装を与えたという事実にもかかわらず、それは十分に効率的ではありませんでした。

配列Aのスライスは、0≤P≤Q <Nであるような整数(P、Q)のペアです。配列Aのスライス(P、Q)は、A [P] + A [P +1] + ... + A [Q-1] + A [Q]はKで割り切れる。

作成を依頼された関数は、Kで割り切れるスライスの数を返す必要がありました。予想される時間の複雑さはO(max(N、K))で、空間の複雑さはO(K)でした。

私の解決策は、最も単純なループであり、ループの内部ですべてのスライスをチェックしました:O(n ^ 2)

私は考えていましたが、どうやってO(max(N、K))でそれを行うことができるのか本当にわかりません。

サブセット合計問題 のバリアントかもしれませんが、すべてのサブ配列をカウントする方法がわかりません。

編集:配列内の要素は負の値になる可能性があります。次に例を示します。

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

Kで割り切れる数値にのみ関心があるので、Kを法とするすべての計算を実行できます。_S[i] = S[0] + S[1] + ... + S[i]_のような累積和配列Sについて考えてみます。次に(P、Q)はK iff _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
_

値iを持つSのxセルであることがわかったら、値iのセルで開始し、値iのセルで終了するスライスの数を数えます。この数はx ( x - 1 ) / 2です。 。エッジの問題を解決するには、値0のセルを1つ追加します。

x ( x - 1 ) / 2の意味:配列が[4、5、0]で、プレフィックスの合計がxであるため4の頻度がxであり、この場合は3であるとします。これで、xの値から、kで割り切れる、またはmod kが0に等しいx-1の数値が少なくともあると結論付けることができます。これらのx-1の数値のうち可能なサブ配列の合計は1 + 2 + 3です。 ... +(x-1)これは_( ( x - 1 ) * ( ( x - 1 ) + 1 ) / 2_です。 (1からNまでの総和の標準式。Nは(x-1)を表します)。

33
Thomash

与えられた数X...

基本的な考え方:

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

そう:

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

次に、右側の合計がXで除算したときに両方の剰余が同じ場合、剰余は相殺され、2つの要素間の要素の合計はXで割り切れます。詳細:

_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
_

これで、Bを_PX + Q_の形式に変換し、Aを_RX + S_の形式に変換できます。一部の整数PQRおよびS、_0 <= Q, S < X_付き。ここで、定義により、QSは、BAをそれぞれXで割った余りになります。

それから私達は持っています:

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

明らかに_(P-R)X_はXで割り切れます(結果は単に_(P-R)_です)。ここで、Xで割り切れるのは_Q - S_だけですが、_0 <= Q, S < X_なので、等しい必要があります。

例:

_B = 13_、_A = 7_、_X = 3_とします。

ここに_B % X = 1_と_A % X = 1_があります。

Bを_4*3 + 1_として書き換え、Aを_2*3 + 1_として書き換えることができます。

次に、_C = 4*3 + 1 - 2*3 - 1 = 2*3_で割り切れる_3_。

ハイレベルなアプローチ:

これまでのすべての数値の累積合計を格納するハッシュマップを作成します_mod X_残りの値が出現する頻度のカウントにマップされます(予想されるO(n)で作成されます)。

_0_の値を1増やします-これは配列の先頭に対応します。

カウントを0に初期化します。

ハッシュマップを調べ、_nC2_(= value!/(2*(value-2)!) )をカウントに追加します。ここで選択する_2_は、サブ配列の開始位置と終了位置です。

カウントは目的の値です。

実行時間:

O(n)が必要です。

例:

_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
_

サブ配列は次のようになります。

_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
_
10
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

あなたの解決策 、@ damluarに感謝しますが、それは非常にきれいです!コメントを追加したいだけです。

  1. 出力は、出力として6ではなく7である必要があります。以下のようにkで割り切れる7つのサブ配列があるため、res += storedArray[0];を追加して修正します。

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

参照リンク

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


    }
0
annya

これは、@ Thomashが提案したソリューションのJava実装です。

2番目のループは必要ありません。現在の値で答えを直接増やしてから増分できるためです。

負の配列インデックスを回避するには、モジュールの計算も調整する必要があります。

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

例:-

入力配列

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

Kは

連続した合計

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

連続する合計配列の上で3で除算

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

1が4つ、2が2つ、0が1つです。

したがって、合計数は(4 * 3)/ 2 +(2 * 1)/ 2 +(2 * 1)/ 2 = 8になります

(4 * 3)/ 2は、4つのうちの任意の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;
    }
0
M Sach