Confused about these asymptotic notation and its runtime - algorithm

public static Asymptotic f3_notation = Asymptotic.BIG_THETA;
public static Runtime f3_runtime = Runtime.LINEAR;
/* When f3 is first called, start will be 0 and end will be the length of the array - 1 */
public int f3(char[] array, int start, int end) {
if (array.length <= 1 || end <= start){
return 1;
}
int mid = start + ((end - start) / 2);
return f3(array, start, mid) + f3(array, mid + 1, end);
}
public static Asymptotic f4_notation = Asymptotic.BIG_THETA;
public static Runtime f4_runtime = Runtime.LINEARITHMIC;
/* When f4 is first called, start will be 0 and end will be the length of the array - 1 */
public int f4(char[] array, int start, int end) {
if (array.length <= 1 || end <= start) return 1;
int counter = 0;
for (int i = start; i < end; i++) {
if (array[i] == 'a') counter++;
}
int mid = start + ((end - start) / 2);
return counter + f4(array, start, mid) + f4(array, mid + 1, end);
}
So I have these two methods. What I don't understand is that both have recursion but why is the first one is linear and the second method is linearithmic?
I was told that if there is division or multiplication, usually its runtime is log-n. Though the first method has the division, it still is considered as linear but the second is not.
The more I understand, the more it confuses me and makes me feel like I know nothing.

The formula for the first method is:
T(n) = 2T(n/2) + O(1)
So if you draw the corresponding tree for this formula you will see that the amount of work is proportional to number of nodes in the tree which is O(n). Also you could use Master Method to solve this.
But for the second it is:
T(n) = 2T(n/2) + O(n)
In fact, what happens here is that your tree will have (just like the first method) O(log n) levels, but here in each level you are spending O(n) time which will result in O(n log n) time complexity. Again, Master Theorem works for this. Note that in the first case, though your tree (for the formula) will have O(log n) levels but in each level you will spend time proportional to the number of nodes on that level, and not O(n).

Related

Is this correct time complexity?

I did this as a solution to one of the leetcode problems, but I'm not sure what the complexity of my algorithm is.
public String countAndSay(int n) {
if (n == 1) return "1";
String pre = countAndSay(n-1);
char[] prev = pre.toCharArray();
int len = prev.length;
if (len == 1 && prev[0] == '1') return "11";
int idx = 1;
int rep = 1;
String res = "";
while (idx <= len) {
if (idx == len) {
res += (Integer.toString(rep) + prev[idx-1]);
break;
}
if (prev[idx-1] == prev[idx]) rep++;
else {
res += (Integer.toString(rep) + prev[idx-1]);
rep = 1;
}
idx++;
}
return res;
}
Since the recursion takes place n times and the loop is O(n), I feel like it should be O(n^2). Is that correct? If not, can you please explain why?
Here are a few facts:
The method calls itself recursively on input n-1.
The method produces the sequence known as look-and-say sequence.
The length of the resulting string grows exponentially with base λ, where λ = 1.303577269034... is Conway's constant, so the length is O(λ^n).
The loop is quadratic on the length of the string (because of the repeated string concatenations), so we have O((λ^n)^2) = O((λ^2)^n) for the loop.
Hence we can derive the following recurrence relation:
T(0) = 1
T(n) = T(n-1) + O((λ^2)^n)
The asymptotic behaviour of this relation is given by
T(n) ∈ Θ((λ^2)^n) = Θ(1.6993^n)
If you use a StringBuilder instead of doing the evil repeated string concatenations, you can get it down to Θ(λ^n) which would also be asymptotically optimal for this problem.

Running Time & Space Complexity Modified Mergesort

This is a homework question for a data structures and algorithms course. I'm not looking to have anyone do my homework for me. Just hoping someone can tell me if I'm approaching this appropriately.
public static void sort(int[] a) {
sort(a, 0, a.length - 1);
}
private static void sort(int[] a, int lo, int hi) {
if (hi <= lo) return;
int[] aux = new int[a.length];
int mid = (hi + lo) / 2;
sort(a, lo, mid);
sort(a, mid + 1, hi);
merge(a, lo, mid, hi, aux);
}
private static void merge(int[] a, int lo, int mid,
int hi, int[] aux) {
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++)
aux[k] = a[k];
for (int k = lo; k <= hi; k++) {
if (i > mid)
a[k] = aux[j++];
else if (j > hi)
a[k] = aux[i++];
else if(aux[j] < aux[i])
a[k] = aux[j++];
else
a[k] = aux[i++];
}
}
The only difference between this implementation and the typical implementation (that has been given in our class), is that the aux array is redefined on every recursive call to sort, versus only being defined once in the public sort method in the typical case. The typical case has running time O(nlog(n)), and a space complexity of O(n).
My task is to determine the running time and space complexity of the shown modified implementation of mergesort. As far as I can tell, the running time is not changed, so it is still O(nlog(n)), and the space complexity is also O(nlog(n)). My logic in coming to this conclusion is that the sort method allocates an array of size n each time it is called, and it is called a total of log(n) times.
So far I've had a difficult time wrapping my head around space and time complexities. Am I thinking about this one correctly, or am I way off?
Any pointers greatly appreciated.
You are right, the space complexity is O(n*logn), but it needs some clarification.
My logic in coming to this conclusion is that the sort method
allocates an array of size n each time it is called, and it is called
a total of log(n) times.
Actually, sort is called a total of n times during the mergesort, but the maximum recursion depth is logn during the process. Let's draw a tree of recursive calls:
sort
/ \
sort sort
/\ /\
sort sort sort sort
...
On each level, each sort function performs half of the work of its parent. So, on level 0 (root node), sort has n iterations, on level 1 each of two sorts has n/2 running time (together it is n), on level 2 each of four sorts has n/4 etc. Combined, each level does n iterations, and since the depth of the tree is log n, you get O(n*logn) time complexity.
However, in your case aux allocates n elements, regardless of the current depth, and there are n invocations of sort, so at the first glance one could conclude that the space complexity is O(n*n) = O(n^2). It is not because once we are finished with each recursive call, allocated space is freed, and there are maximum of logn recursive calls.
Notice that aux unnecessary allocates array of n elements each time. You can improve the algorithm if you declare aux in the merge function and set the appropriate length, something like this:
private static void merge(int[] a, int lo, int mid, int hi) {
int[] aux = new int[hi - lo + 1];
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++)
aux[k - lo] = a[k];
for (int k = lo; k <= hi; k++) {
if (i > mid)
a[k] = aux[j++ - lo];
else if (j > hi)
a[k] = aux[i++ - lo];
else if(aux[j - lo] < aux[i - lo])
a[k] = aux[j++ - lo];
else
a[k] = aux[i++ - lo];
}
}
HTH.

Worst case time complexity for the code

Why is the worst time complexity of the following code is O(N)?
/*
* V is sorted
* V.size() = N
* The function is initially called as searchNumOccurrence(V, k, 0, N-1)
*/
int searchNumOccurrence(vector<int> &V, int k, int start, int end) {
if (start > end) return 0;
int mid = (start + end) / 2;
if (V[mid] < k) return searchNumOccurrence(V, k, mid + 1, end);
if (V[mid] > k) return searchNumOccurrence(V, k, start, mid - 1);
return searchNumOccurrence(V, k, start, mid - 1) + 1 + searchNumOccurrence(V, k, mid + 1, end);
}
What's the worst case? the worst case will be that all element are the same and equals to k. Then you have to at least read all elements, which is N. Since most function calls increase the output by 1, there are about N function calls (some returns 0, but they don't spawn new calls). Therefore, the worst time complexity is O(N).
Yes, in the worst case if all the numbers in the array are equal to k, then in this worst case, the recurrence relation shall be:
T(n) = 2*T(n/2)
This translates into O(n).
The last case -
return searchNumOccurrence(V, k, start, mid - 1) + 1 + searchNumOccurrence(V, k, mid + 1, end);
is the bottleneck step.
Assuming all the values in the array are the same, we get the following relation :
T(N) = 2 * T(N/2) + constant
= 4 * T(N/4) + constant ( 2 * constant = another constant )
= 8 * T(N/8) + constant
.....
= N * T(N/N) + constant
= N + constant
= O(N)

Algorithm complexity

I got this problem "Implement this method to return the sum of the two largest numbers in a given array."
I resolved it in this way:
public static int sumOfTwoLargestElements(int[] a) {
int firstLargest = largest(a, 0, a.length-1);
int firstLarge = a[firstLargest];
a[firstLargest] = -1;
int secondLargest = largest(a, 0, a.length-1);
return firstLarge + a[secondLargest];
}
private static int largest(int s[], int start , int end){
if (end - start == 0){
return end;
}
int a = largest(s, start, start + (end-start)/2) ;
int b = largest(s, start + (end-start)/2+1 , end);
if(s[a] > s[b]) {
return a;
}else {
return b;
}
}
Explanation: I implemented a method 'largeset'. This method is responsible to get the largest number in a given array.
I call the method tow times in the same array. The first call will get the first largest number.I put it aside into variable and i replace it by '-1' number into the array. Then, i call the largest medhod second time.
Some one can tell me what is the complexity of this algo? please
The time complexity of the algorithm is O(n).
Each recursive call's complexity is actually:
f(n) = 2*f(n/2) + CONST
It is easy to see (by induction1) that f(n) <= CONST'*n - and thus it is O(n).
The space complexity is O(logN) - because this is the maximal depth of the recursion - so you allocate O(logN) memory on the call stack.
(1)
If you use f(n) = 2*n*CONST - CONST you get:
f(n) = 2*f(n/2) + CONST = (h.i.) 2*(2*CONST*n/2 - CONST) + CONST =
= 2*n*CONST - 2CONST + CONST = 2*n*CONST - CONST
(Checking the base is is left as exercise for the reader)
The complexity of the algorithm would be measured as O(n).
But the real answer is that your algorithm is WAY more complex, and more expensive in terms of machine resources than it needs to be. And it's WAY more expensive in terms of someone reading your code and figuring out what it's doing.
The complexity of your algorithm should really be on the order of:
public static int sumOfTwoLargestElements(int[] a) {
//TODO handle case when argument is null,
//TODO handle case when array has less than two non-null elements, etc.
int firstLargest = Integer.MIN_VALUE;
int secondLargest = Integer.MIN_VALUE;
for (int v : a) {
if ( v > firstLargest ) {
secondLargest = firstLargest;
firstLargest = v;
} else if ( v > secondLargest ) secondLargest = v;
}
//TODO handle case when sum exceeds Integer.MAX_VALUE;
return firstLargest + secondLargest;
}
The reccurence for 'Largest' method is:
_
f(n) = !
! 1 n = 1
! 2f(n/2) n >=2
!_
If we experiment some few cases, we notice that
f(n) = 2^log(n) When n is power of 2 Rq:Log base 2
Proof:
By induction,
f(1) = 2^log(1) = 2^log(2^0) = 1
We suppose that f(n) = 2^log(n)=n
We show f(2n) = 2^log(2n)= 2n^log(2)=2n
f(2n) = 2*f(2n/2) = 2*f(n)
= 2*2^log(n)
= 2^log(n) + 1
= 2^log(n) + log(2^0)
= 2^log(2n)
= 2n^log(2) by log properties
= 2n
Then f(n) = 2^log(n)=n When n is power of2-smooth function f(2n) < c f(n). it follows smooth function properties that **f(n) = theta of n**

What is the running time of this powerset algorithm

I have an algorithm to compute the powerset of a set using all of the bits between 0 and 2^n:
public static <T> void findPowerSetsBitwise(Set<T> set, Set<Set<T>> results){
T[] arr = (T[]) set.toArray();
int length = arr.length;
for(int i = 0; i < 1<<length; i++){
int k = i;
Set<T> newSubset = new HashSet<T>();
int index = arr.length - 1;
while(k > 0){
if((k & 1) == 1){
newSubset.add(arr[index]);
}
k>>=1;
index --;
}
results.add(newSubset);
}
}
My question is: What is the running time of this algorithm. The loop is running 2^n times and in each iteration the while loop runs lg(i) times. So I think the running time is
T(n) = the sum from i=0 to i=2^n of lg(i)
But I don't know how to simplify this further, I know this can be solved in O(2^n) time (not space) recursively, so I'm wondering if the method above is better or worse than this, timewise as it's better in space.
sigma(lg(i)) where i in (1,2^n)
= lg(1) + lg(2) + ... + lg(2^n)
= lg(1*2*...*2^n)
= lg((2^n)!)
> lg(2^2^n)
= 2^n
thus, the suggested solution is worth in terms of time complexity then the recursive O(2^n) one.
EDIT:
To be exact, we know that for each k - log(k!) is in Theta(klogk), thus for k=2^n we get that lg((2^n)!) is in Theta(2^nlog(2^n) = Theta(n*2^n)
Without attempting to solve or simulate, it is easy to see that this is worse than O(2^n) because this is 2^n * $value where $value is greater than one (for all i > 2) and increases as n increases, so it is obviously not a constant.
Applying Sterling's formula, it is actually O(n*2^n).

Resources