First of all - yes, I have read several posts here on SO, as well as other places about estimating the complexity of an algorithm.
I have read this, this and this, as well as others
I want to try with an algorithm I wrote that finds the largest rectangle, to see if I understood anything at all from what I've read.
public static long getLargestRectangle(long[] arr, int index, long max) {
int count = 1; //1 step * 1
int i = index - 1; //1 step * 1
int j = index + 1; //1 step * 1
if(index == arr.length-1) return max; //1 step * 2
while(i > -1 && arr[index] <= arr[i]) { //1 step * (N+1)
count++; //1 step * N
i--; //1 step * N
}
while(j <= arr.length-1 && arr[index] <= arr[j]) { //1 step * (N+1)
count++; //1 step * N
j++; //1 step * N
}
max = (max < (arr[index] * count) ? (arr[index] * count) : max); //1 step * 7
return getLargestRectangle(arr, index + 1, max); //1 step * 1
}
//total number of steps: 1 + 1 + 1 + (N + 1) + N + N + (N + 1) + N + N + 7
//=> 6N + 12 = O(N) ?
Am I way off here? I'd love some insight.
EDIT
Like this?
T(n) = O(N) + T(n+1)
T(n) = O(N) + O(N) + T(n+2)
T(n) = O(N) + O(N) + O(N) + T(n+3)
T(n) = i * O(N) + (n+i)
T(n) = n * O(N) + (n+n)
= O(N^2)
If this is wrong, I'd really appreciate if you could update your answer and show me.
Am I way off here? I'd love some insight.
I am afraid so :(
return getLargestRectangle(arr, index + 1, max); //1 step * 1
This above is NOT 1 step, it is a recursive invokation of the method. This method "shrinks" the array by 1 element, so this step actually costs T(n-1), where T(.) is the time complexity of the algorithm.
Combining this with what you already have, you get
T(n) = T(n-1) + O(N)
Solving this recurrence formula will give you the algorithm's complexity.
Note: T(n) = T(n-1) + O(N) is a syntatic sugar, it should actually have been T(n) <= T(n-1) + CONST*N for some constant CONST, since you cannot add a set (O(N)) to a scalar (T(n-1)).
Also note: N!=n. n changes over time, N is the initial length of the array. It is so because your algorithm is actually traversing from n (index) to 0, and from n to N.
This does not change the time complexity in terms of big O notation, however.
Related
I have the following algorithm:
for(int i=0; i<list.size(); i++){
String cur = list.get(i);
int cnt =0;
while(output.contains(cur)){
cur= cur.substring(0,5) + String.valueOf(cnt);
cnt++;
}
output.add(cur);
}
Not that "list" and "output" are ArrayLists.
I'm thinking that the Time complexity is O(n^2). But what about the while loop that has a output.contains(cur) inside ?
The complexity of this algorithm seems to be depending on the initial contents of output list:
output is empty, while loop is not executed, complexity is O(N), where N is the size of list
list and output are crafted to comply with output.contains(cur) condition
For example,
List<String> list = Arrays.asList("abcde0", "abcde1", "abcde2", "abcde3", "abcde4", "abcde5", "abcde6");
List<String> output = new ArrayList<>(list);
The size of output will be growing thus the number of iterations will be like this:
1) n+1
2) n+1 + n+2 = 2 (n+1) + 1
3) n+1 + n+2 + n+3 = 3 (n+1) + 3
4) n+1 + n+2 + n+3 + n+4 = 4 (n+1) + 6
...
n) n (n+1) + (1 + n-1)*(n-1)/2 = n (n+1) + n (n - 1)/2 = n (3n + 1)/2
Thus, in this (possibly not the worst case) the complexity can be O(N^2).
public static int[] intSum(int[] arr2) {
int[] arr1 = new int[arr2.length];
for (int i = 0; i < arr2.length; i++) {
for (int j = 0; j <= i; j++) {
arr1[i] += arr2[j];
}
}
return arr1;
}
Given the method, we were told that the inner loop can be summarized using Gauss's sum identity, and as such, the complexity in polynomial form is
T(n) = c_0 + c_1 * n + c_2 * n + (1 + 2 + ... + (n-1)) * c_3
=> T(n) = c_0 + n * c_1 * n * c_2 * (n(n+1))/2 * c_3
I don't really understand this calculation. I get that c_1 * n is the array initialization which is O(n) in Java, and that c_2 * n would be the outer loop, but how does the sum up from 1 to n-1 work here and how is that related to the inner loop?
Ignoring the syntax errors in your code snippet, the inner loop runs from 0 to i, where i increases each time the for is executed again. This gives the sum 0 + 1 + 2 + ... + n.
Now this sum was calculated by the young Gauss in the following way (for even n, but a similar construction works for odd n):
1 + n = n+1
2 + (n-1) = n+1
3 + (n-2) = n+1
...
n/2 + n/2+1 = n+1
So in total there are n/2 lines with a sum of n+1 each. This gives the result n*(n+1)/2
Why is it that inside a for loop and calling a recursive function results to the time complexity of O(2^N) not O(N 2^N) of this code below. Basing on the book CTCI.
void allFib(int n){
for (int i = 0; i < n; i++) {
System.out.println(i + ": "+ fib(i));
}
}
int fib(n){
if (n <= 0) return 0;
else if (n == 1) return 1;
return fib(n - 1) + fib(n -2);
}
Think of your recursive function as computing values in a tree.
fib(n)
/\
/ \
fib(n-1) fib(n-2)
If you look carefully for n = 2, there are 3 values to be computed which is 2^(1+1) - 1 = 3 where 1 here is the height of the tree as in2^(h+1)-1
for n = 3, the height is h = 2
for n = 4, the height is h = 3
For all n, you need to add all of those:
2^2 - 1 + 2^3 - 1 + 2^4 - 1 + ....2^n - 1 -> is of the order of 2^(n+1)
Hence you get O(2^n)
Fill array a from a[0] to a[n-1]: generate random numbers until you get one that is not already in the previous indexes.
This is my implementation:
public static int[] first(int n) {
int[] a = new int[n];
int count = 0;
while (count != n) {
boolean isSame = false;
int rand = r.nextInt(n) + 1;
for (int i = 0; i < n; i++) {
if(a[i] == rand) isSame = true;
}
if (isSame == false){
a[count] = rand;
count++;
}
}
return a;
}
I thought it was N^2 but it's apparently N^2logN and I'm not sure when the log function is considered.
The 0 entry is filled immediately. The 1 entry has probability 1 - 1 / n = (n - 1) / n of getting filled by a random number. So we need on average n / (n - 1) random numbers to fill the second position. In general, for the k entry we need on average n / (n - k) random numbers and for each number we need k comparisons to check if it's unique.
So we need
n * 1 / (n - 1) + n * 2 / (n - 2) + ... + n * (n - 1) / 1
comparisons on average. If we consider the right half of the sum, we see that this half is greater than
n * (n / 2) * (1 / (n / 2) + 1 / (n / 2 - 1) + ... + 1 / 1)
The sum of the fractions is known to be Θ(log(n)) because it's an harmonic series. So the whole sum is Ω(n^2*log(n)). In a similar way, we can show the sum to be O(n^2*log(n)). This means on average we need
Θ(n^2*log(n))
operations.
This is similar to the Coupon Collector problem. You pick from n items until you get one you don't already have. On average, you have O(n log n) attempts (see the link, the analysis is not trivial). and in the worst case, you examine n elements on each of those attempts. This leads to an average complexity of O(N^2 log N)
The algorithm you have is not O(n^2 lg n) because the algorithm you have may loop forever and not finish. Imagine on your first pass, you get some value $X$ and on every subsequent pass, trying to get the second value, you continue to get $X$ forever. We're talking worst case here, after all. That would loop forever. So since your worst case is never finishing, you can't really analyze.
In case you're wondering, if you know that n is always both the size of the array and the upper bound of the values, you can simply do this:
int[] vals = new int[n];
for(int i = 0; i < n; i++) {
vals[i] = i;
}
// fischer yates shuffle
for(int i = n-1; i > 0; i--) {
int idx = rand.nextInt(i + 1);
int t = vals[idx];
vals[idx] = vals[i];
vals[i] = t;
}
One loop down, one loop back. O(n). Simple.
If I'm not mistaken, the log N part comes from this part:
for(int i = 0; i < count; i++){
if(a[i] == rand) isSame = true;
}
Notice that I changed n for count because you know that you have only count elements in your array on each loop.
The problem
Compute the complexity of this algorithm:
for(i=n; i>1;i=i/2)
for(j=i;j<n;j++){
statement;
}
What have I done on this topic before:
The first loop runs logn times.
The second loop runs n-i times with i starting from n and changing to i/2 in each outer loop iteration. So the inner loop runs like this:
n-n 0 times
n - n/2 n/2 times
n - n/4 3n/4 times
n - n/8 7n/8 times
n - n/16 15n/16 times
and so on till
n - 1 times
so the general term is n * ((2^n)-1)/(2^n)
Now this sequence is not arithmetic nor geometric. So formula of n/2 * (a+l) cannot be applied on it. How do I further proceed with this solution or if it is wrong, then what is the correct method.
Note: If n/2*(a+l) is applied, the resulting complexity is -n/(2^n) = O(2^n).
You are on the right track. As you mentioned, the inner loop would run log n times. So, the total number of times it runs is:
(n - n/2) + (n - n/4) + ... (log n) times
= n*(log n) - (n/2 + n/4 + n/8 + ... up to 1)
= n*(log n) - n*(1/2 + 1/4 + ...)
<= n*(log n) - n because (1/2 + 1/4 + ...) is 1 even if we take all terms till infinity (G.P)
= n(log n - 1), which is O(n*log(n))
Remember that when calculating complexity, you are always looking for upper bounds, not exact numbers.
First the calculations
A := (n - n) + (n - n/2) + (n - n/4) + ... + (n - n / 2^logn) =
log n * (n) - n * (1 + 1/2 + 1/4 + 1/8 + .... 1 / 2 ^ logn)
A > log n * (n) - n * (1 + 1/2 + 1/4 + 1/8 + .... + 1 / 2^infity) =
logn * n - n = n(logn - 2)
A < log n * (n)
As you see I have assigned the expression you want to evaluate to A. From the last two inequations it follows the complexity of your algorithm is thetha(n logn)
Here I used the well known geometric progression of (1 + 1/2 + 1/4 + .....) = 2
The exact no of times the statement runs is
nlogn - 2n(1-1/2^logn)