Determine the time complexity of an algorithm - java

I'm reading one cool book, and there was a task to determine the time complexity of the code in the worst case. The code performs integer division (a > 0, b > 0):
int div(int a, int b) {
int count = 0;
int sum = b;
while (sum <= a) {
sum += b;
count++;
}
return count;
}
My answer was O(n), since, in the worst case, when we have a > 0, b = 1, we will iterate through a times in the while() loop.
The answer in the book was O(a/b) which makes sense. But, if we're considering the worst case, then
O(a/1) => O(a) <=> O(n) (letter in parentheses doesn't really matter in this case).
So, the answer O(n) is correct as well. Isn't it?

Alas there is no generalisation of big-O to multi-variable one, see Formal definition of big-O when multiple variables are involved?.
But as you know the exact complexity, T(a,b)=a/b, there is no need for worst-case... We use worst-case analysis when we don't know how to compute the exact complexity.

Your answer does not make sense for the simple reason that there is no n in the problem statement, and saying that a is n is "cheating".
Also, though it is technically correct, it is a little sad to use an untight bound O(a) when you know that the algorithm makes exactly a/b additions.
By the way, it is an arbitrary decision to say that b=1 is "the" worst case. Better say worst case for fixed a (and variable b), or for fixed b (and variable a).

Related

Big O complexity of nested loop depending on changing fraction result

Being relatively new to Big-Oh Notation and complexity analysis, I have been asked to determine the Big-Oh (tightest upper bound) time complexity of the following code.
Now as far as I can tell, for every iteration after the very first one (for which it would only run exactly once) the inner loop runs x2 meaning x times.
In itself this would be O(x).
y is never changed throughout the entire execution of the algorithm. However, n is incremented in every iteration, which affects the exit condition of the outer loop.
Because of the constant incrementation of n, the fraction serving as the exit condition of the outer loop becomes smaller and smaller.
Now if there was something like y/=2, and not y/n, every single time, I would immediately go for O(log y) runtime of the outer loop, but because of the changing denominator I'm thinking that we could view this as a factor, which --according to what I know about Big-Oh-- can be ignored, hence O(y) complexity of the outer loop, meaning O(x*y) complexity of the whole method.
Could anybody provide me with some guidance or a few tips regarding this? Any help would be greatly appreciated.
Thanks a lot!
void a (long x, long y){
long n = 0, x2 = n;
do {
do {
x2--;
} while (x2 > 0);
n++;
x2 = x;
} while (n < y / n);
}
EDIT: Thanks everybody for helping me out. Just as a little follow-up question: What would be the big-o complexity if the code were instead written like this:
void a(int x, int y) {
int n = 1;
do {
x++;
n++;
} while(y/n > x);
}
I tried rearranging it a little (e.g. y > n*x) and thinking of n as a constant that could be dropped which led me to believe that this would be O(y), but I feel like there is something I just don't yet understand about how these fractional conditions can be expressed in big O notation.
Could anybody provide me with some guidance or a few tips regarding this?
When in doubt count the operations performed.
For the purposes of complexity analysis1, it is a safe assumption that any primitive operation (arithmetic, test, branch) takes a constant amount of time. For these purposes, you can further assume that all primitive operations take the same time. So just add them all up.
Then work out an algebraic formula for the number of operations performed as a function of your variables; e.g. your x and y.
The other thing is that in order to figure out the complexity you will need to understand how the code works. Which can be a bit tricky when you have a mystery function like this one.
But even this one can be understood with some analytical thinking. For example, you can work out how many times x2 is going to be decremented in the inner loop ... by looking at the places where x2 is assigned to.
1 - This only applies to complexity analysis. If you are trying to estimate performance, these assumptions are not correct. Different operations take different times, and in some cases the time taken for a given operation could vary depending on the context; e.g. whether or not you get a cache hit. This is part of the reason that even roughly accurate a priori estimates of performance are really hard work.
Assuming the function is actually executed as written, the outer loop runs proportionally to sqrt(y) times, so the complexity of the entire function is O(x * sqrt(y)).
(It's worth noting that in real life, since the function has no side-effects, an optimizing compiler would probably replace it with a function that does nothing.)

Calculate nCr(n,m) mod k for large n efficiently

I need to calculate nCr(n,m) % k for large n (n <= 10^7) efficiently.
Here is my try:
int choose(int n, int m, int k) {
if (n==m || m==0)
return 1 % k;
return (choose(n-1, m-1, k) + choose(n-1, m , k)) % k;
}
It calculates some amount of combinations mod k: nCr(n,m) % k by exploiting pascals identity.
This is too inefficient for large n (try choose(100, 12, 223092870)), I'm not sure if this can be speeded up by memoization or if some totally different number theoretic approach is necessary.
I need this to be executed efficiently for large numbers instantly which is why I'm not sure if memoization is the solution.
Note: k doesn't have to be a prime!
Since nPr has an explicit formula nPr(n, m) = n!/((n-m)!) you should definitely try to use that instead. My tip would be:
Remember that n! = n*(n-1)*...*2*1
Notice that a while loop (yes, loop, not recursion ^^) could greatly optimize the calculation (the division cancels out lots of factors, leaving you with a multiplication nPr(n, m) = n*(n-1)*...*(n-m+2)*(n-m+1))
Finally, you should calculate the modulo after calculating nPr(n, m), to avoid redundant modulo operations.
If it helps, you could try formulating a loop invariant, which is pretty much a statement that should be true for all valid values of n and m.
Hope this helped :)
EDIT
I realized you said nCr after I wrote my answer. For nCr, you could add another while-loop after calculating nPr, that simply calculates m!, divide nPr by m!, and then modulo THAT answer instead. All in all, this would yield an O(n) algorithm, which is pretty scalable. It uses very little memory as well.
This comes up now and then in programming competitions, one common way of solving this is using Lucas' and the Chinese Remainder Theorem.
#DAle posted a useful resource with the details: http://fishi.devtail.io/weblog/2015/06/25/computing-large-binomial-coefficients-modulo-prime-non-prime/

Worst case time complexity of Math.sqrt in java

We have a test exercise where you need to find out whether a given N number is a square of another number or no, with the smallest time complexity.
I wrote:
public static boolean what2(int n) {
double newN = (double)n;
double x = Math.sqrt(newN);
int y = (int)x;
if (y * y == n)
return false;
else
return true;
}
I looked online and specifically on SO to try and find the complexity of sqrt but couldn't find it. This SO post is for C# and says its O(1), and this Java post says its O(1) but could potentially iterate over all doubles.
I'm trying to understand the worst time complexity of this method. All other operations are O(1) so this is the only factor.
Would appreciate any feedback!
Using the floating point conversion is OK because java's int type is 32 bits and java's double type is the IEEE 64 bit format that can represent all values of 32 bit integers exactly.
If you were to implement your function for long, you would need to be more careful because many large long values are not represented exactly as doubles, so taking the square root and converting it to an integer type might not yield the actual square root.
All operations in your implementation execute in constant time, so the complexity of your solution is indeed O(1).
If I understood the question correctly, the Java instruction can be converted by just-in-time-compilation to use the native fsqrt instruction (however I don't know whether this is actually the case), which, according to this table, uses a bounded number of processor cycles, which means that the complexity would be O(1).
java's Math.sqrt actually delegates sqrt to StrictMath.java source code one of its implementations can be found here, by looking at sqrt function, it looks like the complexity is constant time. Look at while(r != 0) loop inside.

How to analyze time complexity here?

Assume you are playing the following Flip Game with your friend: Given a string that contains only these two characters: + and -, you and your friend take turns to flip two consecutive "++" into "--". The game ends when a person can no longer make a move and therefore the other person will be the winner.
Write a function to determine if the starting player can guarantee a win.
For example, given s = "++++", return true. The starting player can guarantee a win by flipping the middle "++" to become "+--+".
Here is my code:
public boolean canWin(String s) {
if(s==null || s.length()<2) return false;
char[] arr=s.toCharArray();
return canWinHelper(arr);
}
public boolean canWinHelper(char[] arr){
for(int i=0; i<arr.length-1; i++){
if(arr[i]=='+' && arr[i+1]=='+'){
arr[i]='-';
arr[i+1]='-';
boolean win=!canWinHelper(arr);
arr[i]='+';
arr[i+1]='+';
if(win) return true;
}
}
return false;
}
It works, but I'm not sure how to calculate the time complexity here since the function will keep calling itself until a false is returned. Anyone share some idea here?
Also during the search, we will encounter duplicate computation, so I think I can use a hashmap to avoid those duplicates. Key: String, Value: Boolean.
My updated code using a hashmap:
public boolean canWin(String s){
if(s==null || s.length()<2) return false;
HashMap<String,Boolean> map=new HashMap<String,Boolean>();
return helper(s,map);
}
public boolean helper(String s, HashMap<String,Boolean> map){
if(map.containsKey(s)) return map.get(s);
for(int i=0; i<s.length()-1; i++){
if(s.charAt(i)=='+' && s.charAt(i+1)=='+'){
String fliped=s.substring(0,i)+"--"+s.substring(i+2);
if(!helper(fliped,map)){
map.put(s,true);
return true;
}
}
}
map.put(s,false);
return false;
}
Still, I wanna know how to analyze the time and space complexity here?
Take that n = arr.length - 1
First pass you have n recursive calls. For each you have removed two +'s so each will have at most n-2 recursive calls, and so on.
So you have at most n+n(n-2)+n(n-2)(n-4)+... recursive calls.
In essence this is n!!(1+1/2+1/(2*4)+1/(2*4*8)+...) Since 1+1/2+1/(2*4)+1/(2*4*8)+... is convergent, ≤2, you have O(n!!)
Regarding memory, you have an array of length n for each recursive call, so you have n + nn + nnn + n ... (n/2 times) ... *n = n(n^(n/2)-1)/(n-1) and this is O(n^(n/2))
This is obviously pointing to not much better performance than with an exhaustive search.
For the hashed improvement, you are asking for all possible combinations that you have managed to create with your code. However, your code is not much different than the code that would actually create all combinations, apart from the fact that you are replacing two +'s with two -'s, which is reducing the complexity by some factor but not the level of it. Overall, the worst case scenario is the same as with the number of combinations of bits among n/2 locations which is 2^(n/2). Observe that hash function itself has probably some hidden log so the total complexity would be for search O(2^(n/2)*ln(n/2)) and memory O(2^(n/2)).
This is the worst case scenario. However, if there are arrangements where you cannot win, when there is no winning strategy, this above is really the complexity you need to count on.
The question of the average scenario is then the question of the number of cases where you can/cannot win and their distribution among all arrangements. This question has not much to do with your algorithm and requires a totally different set of tools in order to be solved.
After a few moments of checking whether the above reasoning is correct and to the point or not, I would be quite happy with the result, since it is telling me all that I need to know. You cannot expect that you will have an arrangement that will be favorable, and I really doubt that you have like only 0.01% of worst case arrangements, so you need to prepare the worst case scenario anyway and unless this is some special project the back-of-the-envelope calculation is your friend.
Anyway, these type of calculations are there to have test cases correctly prepared, not to have a correct and final implementation. Using the tests you can find what the hidden factors in O() really are, taking into account the compiler, memory consumption, pagination and so on.
Still not to leave this as it is, we can always improve the back-of-the-envelope reasoning, of course. For example, you actually do not have n-2 at each step, because it depends on the parity. For example for ++++++++... if you replace third +++--+++++... it is obvious that you are going to have n-3, not n-2 recursive calls, or even n-4. So the half number of calls may have n-3 recursive calls which would be n/2(n-3)+n/2(n-2)=n(n-5/2)
Observe that since n!=n!!(n-1)!! we can take n!!≈√n!, again n!=n!!!(n-1)!!!(n-2)!!! or n!!!≈∛n! This might lead to a conclusion that we should have something like O((n!)^(5/2)). The testing would tell me how much we can reduce x=3 in O((n!)^(x)).
(It is quite normal to look for the complexity in one particular form just like we have O((n!)^(x)), although it can be expressed differently. So I would continue with the complexity form O((n!)^(x)),1≤x≤3)

What is the time complexity for this algorithm?

public static void Comp(int n)
{
int count=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
for(int k=1;k<n;k*=2)
{
count++;
}
}
}
System.out.println(count);
}
Does anyone knows what the time complexity is?
And what is the Big Oh()
Please can u explain this to me, step by step?
Whoever gave you this problem is almost certainly looking for the answer n^2 log(n), for reasons explained by others.
However the question doesn't really make any sense. If n > 2^30, k will overflow, making the inner loop infinite.
Even if we treat this problem as being completely theoretical, and assume n, k and count aren't Java ints, but some theoretical integer type, the answer n^2 log n assumes that the operations ++ and *= have constant time complexity, no matter how many bits are needed to represent the integers. This assumption isn't really valid.
Update
It has been pointed out to me in the comments below that, based on the way the hardware works, it is reasonable to assume that ++, *=2 and < all have constant time complexity, no matter how many bits are required. This invalidates the third paragraph of my answer.
In theory this is O(n^2 * log(n)).
Each of two outer loops is O(n) and the inner one is O(log(n)), because log base 2 of n is the number of times which you have to divide n by 2 to get 1.
Also this is a strict bound, i.e the code is also Θ(n^2 * log(n))
The time complexity is O(n^2 log n). Why? each for-loop is a function of n. And you have to multiply by n for each for loop; except the inner loop which grows as log n. why? for each iteration k is multiplied by 2. Think of merge sort or binary search trees.
details
for the first two loops: summation of 1 from 0 to n, which is n+1 and so the first two loops give (n+1)*(n+1)= n^2+2n+1= O(n^2)
for the k loop, we have k growing as 1,2,4,8,16,32,... so that 2^k = n. Take the log of both sides and you get k=log n
Again, not clear?
So if we set m=0, and a=2 then we get -2^n/-1 why is a=2? because that is the a value for which the series yields 2,4,8,16,...2^k

Categories