Updated my solution here. My previous O(n) solution requires the use of BigInteger, which will not be doable for many languages.
First thing, we observe that
k=1:
[1, 2, 3, 4, 5] = 1C0, 2C1, 3C2, 4C3, 5C4
k=2:
[1, 3, 6, 10, 15] = 2C0, 3C1, 4C2, 5C3, 6C4
k=3:
[1, 4, 10, 20, 35] = 3C0, 4C1, 5C2, 6C3, 7C4
Did you see that pattern?
Our goal is to calculate the sum of (x+k)Cx
where 0 <= x <= n-1
so we can just write this O(n+k)
solution with the help of mod inverse.
private static long solveSmart(int[] A, int k){
++k;
int n = A.length;
long[] inv = new long[n+k];
long[] invf= new long[n+k];
long[] fact= new long[n+k];
fact[0]=fact[1]=inv[1]=invf[1]=invf[0]=1;
for (int i = 2 ; i < n+k; i++){
inv[i]=M-M/i*inv[M%i]%M;
invf[i]=invf[i-1]*inv[i]%M;
fact[i]=fact[i-1]*i%M;
}
long ans = 0;
for (int i = 0; i < n; i++){
ans = (ans + fact[i+k]*invf[i]%M*invf[k]%M)%M;
}
return ans;
}
Here is the benchmark
n = 53607, k = 71789
smart O(n+k): total time taken = 14 ms
naive O(n*k): total time taken = 23524 ms
n = 91366, k = 93929
smart O(n+k): total time taken = 10 ms
naive O(n*k): total time taken = 52764 ms
n = 95464, k = 71555
smart O(n+k): total time taken = 9 ms
naive O(n*k): total time taken = 42117 ms
n = 72726, k = 70765
smart O(n+k): total time taken = 7 ms
naive O(n*k): total time taken = 31727 ms
n = 28557, k = 2818
smart O(n+k): total time taken = 2 ms
naive O(n*k): total time taken = 500 ms
n = 67848, k = 21444
smart O(n+k): total time taken = 4 ms
naive O(n*k): total time taken = 8964 ms
n = 29180, k = 13944
smart O(n+k): total time taken = 4 ms
naive O(n*k): total time taken = 2518 ms
n = 51710, k = 1471
smart O(n+k): total time taken = 2 ms
naive O(n*k): total time taken = 472 ms
n = 78535, k = 9045
smart O(n+k): total time taken = 5 ms
naive O(n*k): total time taken = 4388 ms
n = 49957, k = 29166
smart O(n+k): total time taken = 3 ms
naive O(n*k): total time taken = 8963 ms
And here is the naive O(nk)
code:
private static long solveNaive(int[] A, int k){
int n = A.length;
long ans = 0;
for (int i = 0; i < k; i++){
for (int j = 0, sum=0; j < n; j++){
sum = (sum + A[j])%M;
A[j]=sum;
}
}
for (int i = 0; i < n; i++){
ans = (ans + A[i])%M;
}
return ans;
}
Lastly, here is the test code:
static int M = (int)1e9+7;
public static void main(String[] args){
Random random = new Random();
for (int i = 0; i < 10; i++){
int n = random.nextInt(100000)+1;
int k = random.nextInt(100000);
int[] arr = IntStream.rangeClosed(1, n).toArray();
long a = System.currentTimeMillis();
long smart = solveSmart(arr, k);
long b = System.currentTimeMillis();
long naive = solveNaive(arr, k);
long c = System.currentTimeMillis();
System.out.printf("n = %d, k = %d\n", n, k);
System.out.printf("smart O(n+k): total time taken = %d ms\n", b-a);
System.out.printf("naive O(n*k): total time taken = %d ms\n", c-b);
System.out.println();
if (smart != naive){
throw new IllegalStateException("WHATTTTTTT");
}
}
}
In summary, this problem is testing you about
-
Whether you can observe the pascal nCr
pattern.
-
Whether you know how to compute that efficiently in O(n)
or O(n+k)
with either BigInteger or ModInverse.