I'm trying to figure out what is Big O and big Omega from the following piece of code down below.
This code inputs an array of ints, and sorts them in ascending order.
The worst case would be all in descending order {5,4,3,2,1}
,and the best case would be ascending order {1,2,3,4,5}.
static int counter = 0;
static int counter1 = 0;
static int counter2 = 0;
public static int[] MyAlgorithm(int[]a) {
int n = a.length;
boolean done = true;
int j = 0;
while(j<=n-2) {
counter++;
if(a[j]>a[j+1]) {
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
done = false;
}
j = j+1;
}
j = n-1;
while(j>=1) {
counter1++;
if(a[j]<a[j-1]) {
int temp = a[j-1];
a[j-1] = a[j];
a[j] = temp;
done = false;
}
j = j-1;
}
if(!done) {
counter2++;
MyAlgorithm(a);
}
return a;
}
Worst case for each while loop i got was n-1, and for the recursion it was n/2.
Best case is n-1 while loops, and zero recursion
So my big Omega is (n) ( no recursion )
but for Big O, here is the confusing part for me, since there are n/2 recursion calls, does this mean i do N X N (because of n/2 recursion) big O (n^2)? or does it stay big O(n)???
As you said the Omega is Omega(n). In case all numbers in the array a are already in sorted order the code iterates over the array twice, once per while loop. This are n steps O(1) times.
In the worst case you are correct in assuming O(n^2). As you saw, an array sorted in reverse order produces such a worst case scenario. We can also produce a worst case scenario by having a sorted array in increasing order and then only swap the first and last number. Then each run of MyAlgorithm moves that last/first number two positions. After n/2 steps (runs of MyAlgorithm) the numbers reach their final position. Hence, O(n/2 * n) = O(n^2).
Small side note, sorting in general is in O(n log n), so you can sort something only under some circumstances in O(n).
Related
Working on the following problem:
Given a string s, find the length of the longest substring without repeating characters.
I'm using this brute force solution:
public class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
int res = 0;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
if (checkRepetition(s, i, j)) {
res = Math.max(res, j - i + 1);
}
}
}
return res;
}
private boolean checkRepetition(String s, int start, int end) {
int[] chars = new int[128];
for (int i = start; i <= end; i++) {
char c = s.charAt(i);
chars[c]++;
if (chars[c] > 1) {
return false;
}
}
return true;
}
}
Tbe big O notation is as follows:
I understand that three nested iterations would result in a time complexity O(n^3).
I only see two sigma operators being used on the start of the formula, could someone enlighten me on where the third iteration comes to play in the beginning of the formula?
The first sum from i=0 to n-1 corresponds to the outer for loop of lengthOfLongestSubstring, which you can see iterates from i=0 to n-1.
The second sum from j = i+1 to n corresponds to the second for loop (you could be starting j at i+1 rather than i as there's no need to check length 0 sub-strings).
Generally, we would expect this particular double for loop structure to produce O(n^2) algorithms and a third for loop (from k=j+1 to n) to lead to O(n^3) ones. However, this general rule (k for loops iterating through all k-tuples of indices producing O(n^k) algorithms) is only the case when the work done inside the innermost for loop is constant. This is because having k for loops structured in this way produces O(n^k) total iterations, but you need to multiply the total number of iterations by the work done in each iteration to get the overall complexity.
From this idea, we can see that the reason lengthOfLongestSubstring is O(n^3) is because the work done inside of the body of the second for loop is not constant, but rather is O(n). checkRepitition(s, i, j) iterates from i to j, taking j-i time (hence the expression inside the second term of the sum). O(j-i) time is O(n) time in the worst case because i could be as low as 0, j as high as n, and of course O(n-0) = O(n) (it's not too hard to show that checkRepitions is O(n) in the average case as well).
As mentioned by a commenter, having a linear operation inside the body of your second for loop has the same practical effect in terms of complexity as having a third for loop, which would probably be easier to see as being O(n^3) (you could even imagine the function definition for checkRepitition, including its for loop, being pasted into lengthOfLongestSubstring in place to see the same result). But the basic idea is that doing O(n) work for each of the O(n^2) iterations of the 2 for loops means the total complexity is O(n)*O(n^2) = O(n^3).
I am working on a Java practice interview problem for finding the number of Contiguous Subarrays. I have a working solution, and just want to make sure I understand the time complexity of my answer:
//solution is called with array of n length like this:
countSubarrays([3,4,1,6,2])
int[] countSubarrays(int[] arr) {
System.out.println(Arrays.toString(arr));
int[] ret = new int[arr.length];
//for each index:
for(int x = 0; x < arr.length; x++){
int max = arr[x];
System.out.println("arr["+x+"]="+max);
//try going forward
List<Integer> forwardList = new ArrayList<Integer>();
for(int y = x; y < arr.length; y++){
if(arr[y] <= max){
forwardList.add(arr[y]);
}else{
break;
}
}
System.out.println("forwardList="+forwardList);
//try going backwards
List<Integer> backwardList = new ArrayList<Integer>();
for(int y = x; y >= 0; y--){
if(arr[y] <= max){
//add to start of list
backwardList.add(0, arr[y]);
}else{
break;
}
}
System.out.println("backwardList="+backwardList);
//count number of contiguous subarrays
int count = (forwardList.size() + backwardList.size()) -1;
ret[x]=count;
System.out.println("count: "+count+"\n");
}
return ret;
}
If the input array is of n length, and my code solution features a for loop counting from 0 to n ( int x ), as well as two nested for loops counting forward and backwards until a larger int is found, would this mean my functions time complexity is O(n^2)?
I came to this solution when thinking that in the worst case scenario, my function would be going backwards and forwards the entire length of the array, but this would only happen once inside my int x for loop, so I wasn't sure if the time complexity was O(n) linear time or O(n^2) n squared.
Thanks
The simplified time complexity of this code would be O(n^2), where n is the size of the array. This is because you are iterating through 2 * (1 + 2 + ... + n) times. (There is a 2 because you have 2 for loops inside the first one). This would be O(2n(n-1)/2) = O(n*(n-1)), which simplifies to O(n^2).
This bubble sort is part of my code that sorts an array in increasing order and I swap values with one value between them. I don't know the big O notation of this part of my code. If the length of my array is n, would it be O(n) or O(n^2)?
public static int Sort(int[] b, int begin) {
boolean swapped = false;
int count1 = 0;
int temporary;
while (swapped == false) {
swapped = true;
for (int i = begin; i < b.length - 1; i+=2) {
if (b[i] > b[i + 2]) {
temporary = b[i];
b[i] = b[i + 2];
b[i + 2] = temporary;
swapped = false;
count1++;
}
}
}
return count1;
}
Take the worst case: the array is strictly decreasing. Your code will execute the inner for loop O(n) times, each taking O(n), resulting in a total time complexity of O(n^2). In general, bubblesort and its derivatives are O(n^2).
Hint: Java has Arrays.sort() which uses quicksort for primitives and mergesort for objects, both of which having O(n log n) runtime which is far superior to n^2.
Roughly speaking, it is O(N^2). But there are some important differences between this and a real bubblesort implementation.
It is only going to sort elements from begin onwards.
If begin is even, or will only sort elements at the even positions.
If begin is odd, or will only sort elements at the odd positions.
So in fact, it is partial sort, and it is O(M^2) where M is b.length - begin.
If we set begin to zero, that is O(N^2), but if we set begin to m.length - C then it is O(1) !!!
I already know that the worst case complexity is N, and the best case is Mlog(M) but I just don't see how. Can anybody explain to me why this is the case and what different inputs would cause each case?
public static Iterable<Integer> topM(int[] a, int M){
int N = a.length;
MinPQ<Integer> pq = new MinPQ<Integer>(M+1);
for(int i = 0; i < N; i++){
if(pq.size() < M)
pq.insert(a[i]);
if(pq.min() <= a[i]){
pq.insert(a[i]);
pq.delMin();
}
}
return pq;
}
The complexity is O(Nlog(M)). The worst case is when the array is sorted in a ascending order, in this case each element is inserted to the queue.
The best case is when the array is sorted in a descending order, in this case only the first M elements are inserted. The complexity in the best case is O(N+Mlog(M)).
p.s. the first comment is correct, the second if should be else if.
I have a very general question about calculating time complexity(Big O notation). when people say that the worst time complexity for QuickSort is O(n^2) (picking the first element of the array to be the pivot every time, and array is inversely sorted), which operation do they account for to get O(n^2)? Do people count the comparisons made by the if/else statements? Or do they only count the total number of swaps it makes? Generally how do you know which "steps" to count to calculate Big O notation.
I know this is a very basic question but I've read almost all the articles on google but still haven't figured it out
Worst cases of Quick Sort
Worst case of Quick Sort is when array is inversely sorted, sorted normally and all elements are equal.
Understand Big-Oh
Having said that, let us first understand what Big-Oh of something means.
When we have only and asymptotic upper bound, we use O-notation. For a given function g(n), we denote by O(g(n)) the set of functions,
O(g(n)) = { f(n) : there exist positive c and no,
such that 0<= f(n) <= cg(n) for all n >= no}
How do we calculate Big-Oh?
Big-Oh basically means how program's complexity increases with the input size.
Here is the code:
import java.util.*;
class QuickSort
{
static int partition(int A[],int p,int r)
{
int x = A[r];
int i=p-1;
for(int j=p;j<=r-1;j++)
{
if(A[j]<=x)
{
i++;
int t = A[i];
A[i] = A[j];
A[j] = t;
}
}
int temp = A[i+1];
A[i+1] = A[r];
A[r] = temp;
return i+1;
}
static void quickSort(int A[],int p,int r)
{
if(p<r)
{
int q = partition(A,p,r);
quickSort(A,p,q-1);
quickSort(A,q+1,r);
}
}
public static void main(String[] args) {
int A[] = {5,9,2,7,6,3,8,4,1,0};
quickSort(A,0,9);
Arrays.stream(A).forEach(System.out::println);
}
}
Take into consideration the following statements:
Block 1:
int x = A[r];
int i=p-1;
Block 2:
if(A[j]<=x)
{
i++;
int t = A[i];
A[i] = A[j];
A[j] = t;
}
Block 3:
int temp = A[i+1];
A[i+1] = A[r];
A[r] = temp;
return i+1;
Block 4:
if(p<r)
{
int q = partition(A,p,r);
quickSort(A,p,q-1);
quickSort(A,q+1,r);
}
Assuming each statements take a constant time c. Let's calculate how many times each block is calculated.
The first block is executed 2c times.
The second block is executed 5c times.
The thirst block is executed 4c times.
We write this as O(1) which implies the number of times statement is executed same number of times even when size of input varies. all 2c, 5c and 4c all are O(1).
But, when we add the loop over second block
for(int j=p;j<=r-1;j++)
{
if(A[j]<=x)
{
i++;
int t = A[i];
A[i] = A[j];
A[j] = t;
}
}
It runs for n times (assuming r-p is equal to n, size of the input) i.e., nO(1) times i.e., O(n). But this doesn't run n times everytime. Hence, we have the average case O(log n) i.e, at least log(n) elements are traversed.
We now established that the partition runs O(n) or O(log n). The last block, which is quickSort method, definetly runs in O(n). We can think of it as an enclosing for loop which runs n times. Hence the entire complexity is either O(n2) or O(nlog n).
It is counted mainly on the size (n) that can grow, so for quicksort an array it is the size of the array. How many times do you need to access each elements of the array? if you only need to access each element once then it is a O(n) and so on..
Temp variables / local variables that is growing as the n grows will be counted.
Other variables that is not growing significantly when n grows can be count as constant: O(n) + c = O(n)
Just to add to what others have said, I agree with those who said you count everything, but if I recall correctly from my algorithm classes in college, the swapping overhead is usually minimal compared with the comparison times and in some cases is 0 (if the list in question is already sorted).
For example. the formula for a linear search is
T= K * N / 2.
where T is the total time; K is some constant defining the total computation time; and N is the number of elements in the list.
ON average, the number of comparisons is N/2.
BUT we can rewrite this to the following:
T = (K/2) * N
or redefining K,
T = K * N.
This makes it clear that the time is directly proportional to the size of N, which is what we really care about. As N increases significantly, it becomes the only thing that really matters.
A binary search on the other hand, grows logarithmically (O log(N)).