Time and Space Complexity of a Recursive Algorithm - time-complexity

Convert n to its English words representation, where 0 <= n < 1,000,000,000
Python Solution:
class Solution:
def helper(self, n):
ones = ['', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine']
teens = ['Ten', 'Eleven', 'Twelve', 'Thirteen', 'Fourteen', 'Fifteen', 'Sixteen', 'Seventeen', 'Eighteen', 'Nineteen']
tens = ['', '', 'Twenty', 'Thirty', 'Forty', 'Fifty', 'Sixty', 'Seventy', 'Eighty', 'Ninety']
res = ''
if n < 10:
res = ones[n]
elif n < 20:
res = teens[n - 10]
elif n < 100:
res = tens[n // 10] + ' ' + self.helper(n % 10)
elif n < 1000:
res = self.helper(n // 100) + ' Hundred ' + self.helper(n % 100)
elif n < 1000000:
res = self.helper(n // 1000) + ' Thousand ' + self.helper(n % 1000)
elif n < 1000000000:
res = self.helper(n // 1000000) + ' Million ' + self.helper(n % 1000000)
return res.strip()
def convert_to_word(self, n):
if n == 0:
return 'Zero'
return self.helper(n)
I've been trying to calculate the time and space complexity of this solution. I've seen different answers. Some say that the time complexity is O(1) because the helper function is called a fixed number of times (even if n is a large number). Others say it's O(log(n)).
The space complexity seems to be O(1)?
I am so confused. Please help me clarify. Thank you.

On all inputs n that are larger than 1000000000, the function returns immediately with an empty string, without any computations or recursive calls. So of course the time complexity is O(1), likewise the space complexity (since what happens for smaller n is completely irrelevant).
The case would be different and more interesting if you removed the line
elif n < 1000000000
so that for large n you would get a (virtually) unbounded resulting string with an unbounded number of Million-substrings (ignoring the fact that integers have a maximum size on a real computer, and ignoring the fact that you would get nonsensical number words). In this case you would get the time complexity O(log(n)^2) (since you're concatenating O(log n) strings of length O(log n)) and space complexity O(log n) (because of the call stack for the recursive calls). The time complexity could easily be reduced to O(log n) by handling the string concatenations more efficiently.
Some additional explanation
It seems from the comments that it's not obvious why the time complexity is O(1). If we say that the time complexity T(n) is in O(1), that means that there exists a constant c and a constant k such that for all n > k, T(n) <= c.
In this example, choosing c = 9 and k = 0 does the trick, or alternatively choosing c = 1 and k = 1000000000, hence the time complexity is O(1). It should now also be clear that the following function is also O(1) (albeit with a very large hidden constant factor):
void f(int n) {
if (n < 1000000000) {
for(int i = 0; i < n; i++) {
for(int k = 0; k < n; k++)
print(i);
}
}
else print("");
}

Related

How to find time and space complexity for this algorithm?

I need to find time and space complexity for this java code for recursive calculation of the determinant of a matrix:
public int determinant(int[][] m) {
int n = m.length;
if(n == 1) {
return m[0][0];
} else {
int det = 0;
for(int j = 0; j < n; j++) {
det += Math.pow(-1, j) * m[0][j] * determinant(minor(m, 0, j));
}
return det;
}
}
public int[][] minor(final int[][] m, final int i, final int j) {
int n = m.length;
int[][] minor = new int[n - 1][n - 1];
int r = 0, s = 0;
for(int k = 0; k < n; k++) {
int[] row = m[k];
if(k != i) {
for(int l = 0; l < row.length; l++) {
if(l != j) {
minor[r][s++] = row[l];
}
}
r++;
s = 0;
}
}
return minor;
}
Help: Determine the number of operations and memory consumption of the algorithm with respect to n, and after dividing by n^2 you will get the desired result.
I'm confused. I calculate the time complexity as the sum of the input size (n^2) and the number of steps, and the space complexity as the input size. (?) So its O(n^2) but I don't think I'm doing it right. And why should I divide it (help is from the teacher).
Can someone explain to me how to calculate this in this case?
Let us see. For an input matrix of size n, denote the time complexity as T(n).
So, for a matrix of size n:
if n = 1, we have the answer right away: T(1) = O(1);
otherwise, we loop over j for a total of n times, and for each j, we:
construct a minor in O(n^2) (the minor function), and then
run the function recursively for that minor: this one takes T(n-1) time.
Putting it all together, we have T(1) = O(1) and T(n) = n * (n^2 + T(n-1)).
To understand what is going on, write down what is T(n-1) there:
T(n) = n * (n^2 + T(n-1)) =
= n * (n^2 + (n-1) * ((n-1)^2 + T(n-2)))
And then, do the same for T(n-2):
T(n) = n * (n^2 + T(n-1)) =
= n * (n^2 + (n-1) * ((n-1)^2 + T(n-2))) =
= n * (n^2 + (n-1) * ((n-1)^2 + (n-2) * ((n-2)^2 + T(n-3)))) = ...
Then we write what is T(n-3), and so on.
Now, open the brackets:
T(n) = n * (n^2 + (n-1) * ((n-1)^2 + (n-2) * ((n-2)^2 + T(n-3)))) =
= n^3 + n * (n-1) * ((n-1)^2 + (n-2) * ((n-2)^2 + T(n-3)))) =
= n^3 + n * (n-1)^3 + n * (n-1) * (n-2) * ((n-2)^2 + T(n-3)))) =
= n^3 + n * (n-1)^3 + n * (n-1) * (n-2)^3 + n * (n-1) * (n-2) * T(n-3)))) =...
As we can see going further, the highest term among these, in terms of n, will be
T(n) = n * (n-1) * (n-2) * ... * 3 * 2 * 1^3, which is just n! (n-factorial).
The above is about time complexity.
To address space complexity, consider how recursion uses memory.
Note that it is much different from what happens to the time.
The reason is that, at each point of time, we are in some single particular branch of the recursion tree, and at this time other branches don't use the memory they need.
Therefore, unlike with time, we can't just add up the total memory used by all recursive calls.
Instead, we have to consider all branches of the recursion tree, and find the one that uses maximum memory.
In this particular case, it is just any deepest branch of the recursion.
Denote the memory consumption by M(n) where n is the matrix size.
if n = 1, we have the answer right away: M(1) = O(1);
otherwise, we loop over j for a total of n times, and for each j, we:
construct a minor that takes O(n^2) memory (the minor function), and then
run the function recursively for that minor: this one takes M(n-1) memory.
Note that the loop does not accumulate memory used by minors.
Instead, when we construct the minor for next j, the minor for previous j is not needed anymore.
Thus we have M(n) = n^2 + M(n-1).
Again, writing down what is M(n-1), then M(n-2), and so on gets us to
M(n) = n^2 + (n-1)^2 + (n-2)^2 + ... + 3^2 + 2^2 + 1^2, which is O(n^3) with some constant factor we need not care about.
So, by the above reasoning, the answer is: T(n) = O(n!) and M(n) = O(n^3).
What's the hint about, with dividing by n^2, I don't have a clue, sorry!

Time complexity of below pseudocode

void buzz(int num, int [] a)
{
for(int i=1;i<num;i*=2){
for(int j=0;j<a[i]%num;j++){
print(num);
}
}
}
I understand that the outer for loop runs for log n times. Need help in understanding the complexity of the inner loop and the overall.
Let us assume num = 2^n for simplicity.
The inner loop runs a[i] % num times, which is comprised in range [0, num-1]. The outer loop runs n times so you can conclude O(num.log(num)) = O(n.2^n) worst-case complexity.
For a precise evaluation, let r[i] := a[i] % num. Then the number of print's is
r[1] + r[2] + r[4] + r[8] + ... r[2^(n-1)].
This is n times the average r (for the relevant indexes; the expectation of r could be (num-1)/2)).

What is the time function of this loop

For (i=0, i < n, i++) // n+1
{
for(j=0; j < n, j++) // n * (n+1)
{
C[i][j]+ B[i,j]; // n * n
}
}
The instructor said the time function is f(n) = 2n^2 +2n +1
How come it's not 2n^2+ 2n + 2 because both loops run one extra time when i=n and j=n?
Each time we increment i, we check if the new value becomes equal to or greater than n, so we perform the comparison n + 1 times (n times when i <n and an additional comparison when i becomes equal to n). the same thing for j => (n+1) * (n times when i<n).
So, n*n+(n+1)*n+(n+1)=2n^2+ 2n +1

Time complexity of this code?

So these are the for loops that I have to find the time complexity, but I am not really clearly understood how to calculate.
for (int i = n; i > 1; i /= 3) {
for (int j = 0; j < n; j += 2) {
... ...
}
for (int k = 2; k < n; k = (k * k) {
...
}
}
For the first line, (int i = n; i > 1; i /= 3), keeps diving i by 3 and if i is less than 1 then the loop stops there, right?
But what is the time complexity of that? I think it is n, but I am not really sure. The reason why I am thinking it is n is, if I assume that n is 30 then i will be like 30, 10, 3, 1 then the loop stops. It runs n times, doesn't it?
And for the last for loop, I think its time complexity is also n because what it does is
k starts as 2 and keeps multiplying itself to itself until k is greater than n.
So if n is 20, k will be like 2, 4, 16 then stop. It runs n times too.
I don't really think I am understanding this kind of questions because time complexity can be log(n) or n^2 or etc but all I see is n.
I don't really know when it comes to log or square. Or anything else.
Every for loop runs n times, I think. How can log or square be involved?
Can anyone help me understanding this? Please.
If you want to calculate the time complexity of an algorithm, go through this post here: How to find time complexity of an algorithm
That said, the way you're thinking about algorithm complexity is small and linear. It helps to think about it in orders of magnitude, then plot it that way. If you take:
x, z = 0
for (int i = n; i > 1; i /= 3) {
for (int j = 0; j < n; j += 2) {
x = x + 1
}
for (int k = 2; k < n; k = (k * k) {
z = z + 1
}
}
and plot x and z on a graph where n goes from 1 -> 10 -> 100 -> 1000 -> 10^15 or so, you'll get an answer which looks like an n^2 graph. When analyzing algorithmic complexity you're primarily interested in maximum the number of times, in either the worst or most common case, your inputs are looped through omitting constants. So in this case I would expect your algorithm to be O(n^2)
For further reading, I suggest https://en.wikipedia.org/wiki/Introduction_to_Algorithms ; it's not exactly easy but covers this in depth.

Time Complexity: O(logN) or O(N)?

I thought the time complexity of the following code is O(log N), but the answer says it's O(N). I wonder why:
int count = 0;
for (int i = N; i > 0; i /= 2) {
for (int j = 0; j < i; j++) {
count += 1;
}
}
For the inners for-loop, it runs for this many times:
N + N/2 + N/4 ...
it seems to be logN to me. Please help me understand why here. Thanks
1, 1/2, 1/4, 1/8... 1/2 ** n is a geometric sequence with a = 1, r = 1/2 (a is the first term, and r is the common ratio).
Its sum can be calculated using the following formula:
In this case, the limit of the sum is 2, so:
n + n/2 + n/4 ... = n(1 + 1/2 + 1/4...) -> n * 2
Thus the complicity is O(N)
Proceeding step by step, based on the code fragment, we obtain: