Running Time of this Simple Program - Time Complexity - java

I am trying to figure out what the time complexity of this simple program is, but I can't seem to understand what would be the best way to do this.
I have written down the time complexity side by side for each line
1 public int fnA (int n) {
2 int sum = 0; O(1)
3 for (int i = 0; i < n; i++) { O(n)
4 int j = i; O(n)
5 int product = 1; O(1)
6
7 while (j > 1) { O(n)
8 product ∗= j; O(log n)
9 j = j / 2; O(log n)
10 }
11 sum += product; O(1)
12 }
13 return sum; O(1)
14 }
Am I correct to assume these running times and that the final running time is: O(n)
If not, would somebody be able to explain where it is I am going wrong?
Overall:
1 + n + n + 1 + n + logn + logn + 1 + 1
= 3n + 2logn + 4
Final: O(n)

Time complexity for that algorithm is O(NlogN).
The for loop is executed N times (from 0 to N).
The while loop is executed logN times since your are dividing the number to half each time.
Since your are executing the while inside the for, your are executing a logN operation N times, from there it is the O(NlogN).
All remaining operations (assign, multiplication, division, sum) you can assume that takes O(1)

The crux of the above program is the while loop and it is the defining factor and rest of the lines will not have complexity more than O(n) and assuming that arithmetic operations will run in O(1) time.
while (j > 1) {
product ∗= j;
j = j / 2;
}
The above loop will have a run time of O(log(j)) and j is varying from 1 to n, so its the series...
-> O(log(1) + log(2) + log(3) + log(4).....log(n))
-> O(log(1*2*3*4...*n))
-> O(log(n!))
and O(log(n!)) is equal to O(n log(n))
For the proof for above refer this

No for every i, there is logn loop running and hence for n elements the total complexity is nlogn.
Since you know that the following loop takes logn .
while (j > 1) {
product ∗= j;
j = j / 2;
}
Now this particular loop is executed for every i. And so this will be executed n times. So it becomes nlogn.

To start with, you could count all operations. For example:
1 public int fnA (int n) {
2 int sum = 0; 1
3 for (int i = 0; i < n; i++) {
4 int j = i; n
5 int product = 1; n
6
7 while (j > 1) {
8 product ∗= j; ?
9 j = j / 2; ?
10 }
11 sum += product; n
12 }
13 return sum; 1
14 }
Now we could do the counting: which sums up to: 2 + 3n + nlog(n)
In a lot of programs the counting is more complex and usually has one outstanding higher order term, for example: 2+3n+2n2. When talking about performance we really care about when n is large, because when n is small, the sum is small anyway. When n is large, higher order term drawf the rest, so in this example 2n2 is really the term that matters. So that's the concept of tilde approximation.
With that in mind, usually one could quickly identify the portion of code that gets executed most often and use its count to represent overall time complexity. In example given by OP, it would look like this:
for (int i = 0; i < n; i++) {
for (int j = i; j > 1; j /= 2)
product *= j;
}
which gives ∑log2n. Usually the counting involves discrete mathamatics, one trick I have learned is to just replace with it integral and do caculus: ∫ log2n = nlog(n)

Related

Nested Loop Time Complexity for( i = n; i > 0; i /= 2) VS for( i = n; i > 0; i/=2) for( j = 0; j < i; j++)

I'm not able to find out Algo A and Algo B's time complexity, Please help me Guys !!!
Algo A :
for(int i=n; i>=1; i/=2)
some statement
If I'm not wrong,
i = n;
i = n / 2 to the power of 1;
i = n / 2 to the power of 2;
i = n / 2 to the power of 3;
i = n / 2 to the power of 4;
.................
.................
i = n / 2 to the power k;
Algo A terminate when,
n / 2 to the power of k < 1
Therefore k = log n, Algo A take logn time;
Algo B :
for(i=n; i>=1; i/=2)
for(j=0; j<i; j++)
some statement
Guys I'm not able to find out Algo B's time complexity so how to calculate this and correct me if I'm wrong with Algo A
Short answer: if "some statement" runs in constant time, then algorithm B runs in O(n).
Let us first analyze the inner loop:
for(j=0; j<i; j++)
some statement
Since j iterates from 0 (inclusive) to i (exclusive), it thus means it will thus perform i operations.
Now we can analyze the outer part:
for(i=n; i>=1; i/=2)
// i operations
Here i thus starts with n, is each time divided by 2, and each iteration, we perform i tasks.
This thus means that the total number of tasks is:
n + n/2 + n/4 + n/8 + ... + 1
The above is a known sequence:
m
---
\ -k -m
/ n * 2 = (2 - 2 ) n
---
k=0
Here k thus ranges from 0 to log2n, and thus the total number of instructions is (2-2log2n)× n or (2 - 1/n)× n and thus 2× n - 1. We can simplify that to O(n).

Finding a Prime Numbers

I have an array of length N=10^5 For each index 1<=i<=n I have to calculate the difference between A[j]-A[i] and (j-i) is prime and j>i
Here is my code:
for(int i=1;i<=n;i++){
for(int j=0;j<prime.size();j++){
int x = prime.get(j);
if(x+i>n) break;
ans+= A[x+i]-A[i];
}
}
How should i make this work even faster ? I think the time complexity is O(N*prime.size)
First, I will rephrase your question so that it states what I believe you want to achieve. You are probably looking for the sum of the differences of the form A[j]-A[i], where (j-i) is a "positive" prime and 1<=i<j<=N. With this statement in mind...
We count the number of times A[k] is added to the sum (denoted by p) and the number of times A[k] is subtracted from the sum (denoted by m). Well, m is equal to the number of primes in the interval [1,N-k], while p is equal to the number of primes in the interval [1,k-1]. If you don't believe me, simulate step-by-step what your code does. Then you can do:
S = 0
for k = 1 to N do
S = S + (p(k) - m(k)) * A[k]
endfor
Now, we need to find a way to determine p and m efficiently for each k in the interval [1,N]. I see you have already constructed what seems to be an ordered list of primes. So, to answer a query of the form 'how many primes in the interval [1,t]?' you could perform a binary search on that list for t. This would get the complexity down to O(N*log(prime.size)).
As an alternative, you can pre-compute the answers to the queries of the form 'how many primes in the interval [1,t]?'. You need an extra array nrPrimesLessThan of size N to keep the results, doing something like this to compute its values:
count = 0
for i = 1 to N do
if i < prime.get(count) then
nrPrimesLessThan[i] = count
else
count = count + 1
nrPrimesLessThan[i] = count
endif
endfor
The pre-computation part takes O(N) steps, but now one query takes O(1) steps, thus the calculating the sum takes O(N) steps. Overall, linear time in N.
Judging from your code example, you want to sum the differences of all value pairs in the array for which the difference of the indices is prime. You've got already an array of primes.
The diagram below shows how the elements get subtracted and added:
0 1 2 3 4 5 6 7 8 9
- + + + + 0
- + + + + 1
- + + + + 2
- + + + 3
- + + + 4
- + + 5
- + + 6
- + 7
- 8
- 9
A + means an element is added to the overall sum. A - means the element is subtracted from the sum. This is not a single subtraction; the subtraction happens for each addition to its left, so A[0] is subtracted 4 times. It is never added.
On the other hand, A[9] is never subtracted, but added four times. In general, each element is subtracted as many times as there are plusses in a row and it is added as many times as there are plusses in a columns. There is a symmetry here:
add[i] = sub[N - i - 1]
for zero-based indices. What is the value of add[i]? It is the number of primes that are smaller or equal to i.
Here's a code example where the add array is called m:
int psum2(const int A[], int n)
{
int sum = 0;
int m[n];
int j = 0;
int k = 0;
for (int i = 0; i < n; i++) {
if (i == prime[j]) {
k++;
j++;
}
m[i] = k;
}
for (int i = 0; i < n; i++) {
sum += (m[i] - m[n - i - 1]) * A[i];
}
return sum;
}
The array m is always the same and can be precalculated if you need to perform the sum more often. The algorithm is O(n).
The problem is also unrelated to primes at its core. The method above works for all conditional sums of differences where the difference of the indices must be conteined in a certain set of numbers.

sum of absolute matrix

Here is a question from previous HackerEarth Challenge -
Roy has a matrix of size NxN. Rows and Columns are numbered from 0 to N-1.
jth column of ith row contains absolute difference between i and j.
In other words, Matrix[i][j] = abs(i-j) where 0 ≤ i, j < N.
Your task is to find sum of this matrix i.e.
sum = 0
for i=0 to N-1
for j=0 to N-1
sum += Matrix[i][j]
and here is my solution to this problem -
public static long getSum(int num, long acc) {
if (num == 1)
return acc;
long sum = 0;
for (int i = 0; i < num; i++) {
sum += i;
}
sum = sum * 2;
return getSum(num - 1, acc + sum);
}
But this function fails for large number like say anything greater than 4500. I get Stack Over Flow Error.
Here I have tried two things basically to keep the code optimized -
use tail recursion, and
keep running time of this function of order 'n'
So please tell me if I have achieved the two things here correctly. If yes what else can I do to optimize this code. Thanks for your help.
The matrix has very simple structure (I draw only top right half, bottom left is the same, mirrored)
0 1 2 3 4 5 ...
. 0 1 2 3 4 ...
. . 0 1 2 3 ...
. . . 0 1 2 ...
. . . . 0 1 ...
. . . . . 0 ...
It is clear that Kth row contains arithmetic progression 0..(N - K - 1), so it's sum is
Sk = (N - K - 1) * (N - K) / 2
and overall sum is (O(N) solution)
S = 2 * Sum[k = 0..N-1] (Sk)
Moreover, while sum of every row is 'triangular' number, sum of triangular numbers is 'Tetrahedral number', and there is closed formula, that leads to O(1) solution
S = 2 * ((N-1)*N*(N+1)/6) = N*(N+1)*(N-1)/3
Example: for N=4 S = 3*4*5/3 = 20
Tail recursion
Tail recursion does not protect you from stack overflow in Java. Some other languages can recognise tail-calls and optimize them during compilation so they do not extend the stack.
...tail call optimization is hard to do in the JVM because of the security model and the need to always have a stack trace available.
(From Does the JVM prevent tail call optimizations?)
It is however easy to replace tail recursions with a loop:
public static long getSum(int num, long acc) {
while (num > 1) {
long sum = 0;
for (int i = 0; i < num; i++) {
sum += i;
}
sum = sum * 2;
//set up values for next loop
num--;
acc += sum;
}
return acc;
}
Big-O
keep running time of this function of order 'n'
You have not achieved this. It is clear to see there are 2 nested loops over num, and num is decreasing, I think this makes it O(n log n)

Someone explain to me a few step of this Java Big O code

for( int bound = 1; bound <= n; bound *= 2 ) {
for( int i = 0; i < bound; i++ ) {
for( int j = 0; j < n; j += 2 ) {
... // constant number of operations
}
for( int j = 1; j < n; j *= 2 ) {
... // constant number of operations
}
}
}
The correct answer is O(n^2).
I know the third for loop has complexity O(n+2) , The fourth for loop has complexity O(log n), since two loops are not nested, they are added together right? So what should I do with the first two loops, I know it's log(n) and n. so my question what should be the next step, how do I know which loop to add or multiply. Basically I'm just confused how they get to O(n^2).
The value of bound in the first loop doubles each iteration up to n: 1, 2, 4 ... n
The second loop runs up to the value of bound, totaling: 1 + 2 + 4 + ... + n = O(n)
The third and fourth loops are O(n) and O(logn), which added together is just O(n) because n dominates logn.
So the first two loops together are O(n) and the inner two loops are O(n). Multiplied together, they're O(n^2).
The rules for O is O(a + b) = O(max(a, b)), O(const * a) = O(a). (max is in complexity not as in actual numbers)
So the innermost loops are both O(n), together they are O(n + n) = O(max(n, n)) = O(n).
The middle loop is O(n) but the outer one is O(log n) this comes out as O(n * n * log n). This is not an error O is only an upper limit for the operations, anything O(n) is O(n*n) too.
But we can do better!
Each time the middle loop executes, it double the number of operations it does. it starts out as 1 next is 2, 4, 8 etc. together both the middle and the outermost loop execute 1 + 2 + 4 + 8 + ... + n operations. this is O(n + m - 1), where m is the first power of two after n - 1. but because of that n <= m < 2 * n so m = O(n), O(n + m) = O(n + n) = O(n)
so the outmost and the middle loop together are O(n) and the innermost is also O(n), and that means that the whole thing is O(n * n) or O(n ^ 2) as you wrote

Logarithmic and other for loop converted to sum notation

I need help with for loops converted to a sum nations. Some of are easy but others are a bit tricky. I need getting the sum notation setup correctly.
Like this: (Correct for example loop)
As an example:
for (int i = 0; i < n; i = i + 1)
a = i; //cost 1
Sum 1, i=0 to n-1 == n.
I need help with following:
Logarithmic (just the correct sum notation)
for (int i = 0; i < n; i = 2 * i)
a = i; //cost 1
Sum 1, i=0 to log(n)-1 == log n. Correct??
Triple nested (both sum notation and step by step why it ends up like it)
for (int i = 0; i < n; i = i + 1)
for (int j = 0; j <= i; j = j + 1)
for (int k = 0; k <= j; k = k + 1)
a=i; //cost 1
The triple nested loop
I'll give a simple, but a very useful method to analyze such summations in terms of the asymptotic notation.
This is a very general method, you can use it to bound many multiple index summations without a big effort.
Upper bound
First of all, let's derive an upper bound for the sum. It's quite easy:
Lower bound
The trick in this case is to derive the lower bound. The rule of thumb is to reduce the summation range incrementing the lower summation index to the fraction of the upper index and then substitute the new lower index as the upper index in the nested loop. It's also pretty easy:
Putting it together
From both inequalities, you can deduce that:
Which in terms of the asymptotic analysis gives:
As you can see, your triple nested loop has the same asymptotic time complexity as:
for(int i = 0; i < n; i = i + 1)
for(int j = 0; j < n; j = j + 1)
for(int k = 0; k < n; k = k + 1)
//operations of cost 1
For the logarithmic loop:
First, you can't initialize the index with zero when dealing with logarithmic loops.
Second, the following is the way to present the algorithmic loop using Sigma notation:
Look at the last slide of this document of Dr. Jauhar.
For the three nested loops:
Mark Allen Weiss work may help you considerably. See this link.
Logarithmic
The second for-loop will never stop (0 * 2 = 0). I guess, you were asking about this loop:
for (int i = 1; i < n; i = 2 * i)
a = i; //cost 1
In this case the complexity expressed via the sum notation will be:
Sum 1, i=1 to log(n-1) == O(log n)
Triple nested
In this case it will be the summation of:
number of steps sum
--------------------------------------
1 1 1 1 1 1 . n
2 2 2 2 2 . 2(n-1)
3 3 3 3 . 3(n-2)
4 4 4 . 4(n-3)
. . . .
n-1 n-1 2(n-1)
n n
or alternatively if I transpose the triangle:
number of steps sum
--------------------------------------
1 2 3 4 . n-1 n n(n+1)/2
1 2 3 4 . n-1 (n-1)(n)/2
1 2 3 4 . (n-2)(n-1)/2
1 2 3 . 4(n-3)
1 2 . .
1 . 3
. 1
The numbers on the right side (in the second triangle) are also called triangle numbers. So the question is equivalent to
"What is the sum of triangle numbers lower or equal than f(n). (f(1) + f(2) + f(n), where f(x) = x(x+1)/2)."
The answer to this question is
f(n) = n(n+1)(n+2)/6
The proof is here.
So the resulting complexity in big-o is O(n^3)

Categories

Resources