Time complexity of the word break recursive solution? - algorithm

What is the time complexity of the recursive solution to this in the code taken from: http://www.geeksforgeeks.org/dynamic-programming-set-32-word-break-problem/ :
// returns true if string can be segmented into space separated
// words, otherwise returns false
bool wordBreak(string str)
{
int size = str.size();
// Base case
if (size == 0) return true;
// Try all prefixes of lengths from 1 to size
for (int i=1; i<=size; i++)
{
// The parameter for dictionaryContains is str.substr(0, i)
// str.substr(0, i) which is prefix (of input string) of
// length 'i'. We first check whether current prefix is in
// dictionary. Then we recursively check for remaining string
// str.substr(i, size-i) which is suffix of length size-i
if (dictionaryContains( str.substr(0, i) ) &&
wordBreak( str.substr(i, size-i) ))
return true;
}
// If we have tried all prefixes and none of them worked
return false;
}
I'm thinking its n^2 because for n calls to the method, it worst case does (n-1) work (iterates over the rest of string recursively?). Or is it exponential/n!?
I'm having a tough time figuring out Big(O) of recursive functions such as these. Any help is much appreciated!

The answer is exponential, to be precise O(2^(n-2)). (2 power n-2)
In each call you are calling the recursive function with length 1,2....n-1(in worst case). To do the work of length n you are recursively doing the work of all the strings of length n-1, n-2, ..... 1. So T(n) is the time complexity of your current call, you are internally doing a work of sum of T(n-1),T(n-2)....T(1).
Mathematically :
T(n) = T(n-1) + T(n-2) +.....T(1);
T(1) = T(2) = 1
If you really don't know how to solve this, an easier way to solve the above recurrence is by just substitute values.
T(1) = T(2) = 1
T(3) = T(1) + T(2) = 1+1 =2; // 2^1
T(4) = T(1)+ T(2) + T(3) = 1+1+2 =4; //2^2
T(5) = T(1) + T(2) +T(3) +T(4) = 1+1+2+4 =8; //2^3
So if you substitute first few values, it will be clear that the Time complexity is 2^(n-2)

The short version:
The worst-case runtime of this function is Θ(2n), which is surprising because it ignores the quadratic amount of work done by each recursive call simply splitting the string into pieces and checking which prefixes are words.
The longer version: let's imagine we have an input string consisting of n copies of the letter a followed by the letter b. (we'll abbreviate this as aⁿb), and create a dictionary containing the words a, aa, aaa, ..., aⁿ.
Now, what will the recursion do?
First, notice that none of the recursive calls will return true, because there's no way to account for the b at the end of the string. This means that each recursive call will be to a string of the form aᵏb. Let's denote the amount of time required to process such a string as T(k). Each one of these calls will fire off k smaller calls, one to each suffix of aᵏb.
However, we also have to account for the other contributors to the runtime. In particular, calling string::substr to form a substring of length k takes time O(k). We also need to factor in the cost of checking if a prefix is a word. The code isn't shown here for how to do this, but assuming we use a trie or a hash table we can assume that the cost of checking if a string of length k is a word is O(k) as well. This means that, at each point where we make a recursive call, we will do O(n) work - some amount of work to check if the prefix is a word, and some amount of work to form the substring corresponding to the suffix.
Therefore, we get that
T(k) = T(0) + T(1) + T(2) + ... + T(k-1) + O(k2)
Here, the first part of the recurrence corresponds to each of the recursive calls, and the second part of the recurrence accounts for the cost of making each of the substrings. (There are n substrings, each of which takes time O(n) to process). Our goal is to solve this recurrence, and just for simplicity we'll assume that T(0) = 1.
To do this, we'll use the "expand and contract" technique. Let's write out the values of T(k) and T(k+1) next to each other:
T(k) = T(0) + T(1) + T(2) + ... + T(k-1) + O(k2)
T(k+1) = T(0) + T(1) + T(2) + ... + T(k-1) + T(k) + O(k2)
Subtracting this first expression from the second gives us that
T(k+1) - T(k) = T(k) + O(k),
or that
T(k+1) = 2T(k) + O(k).
How did we get O(k) out of the difference of two O(k2) terms? It's because (k + 1)2 - k2 = 2k + 1 = O(k).
This is an easier recurrence to work with, since each term just depends on the previous one. For simplicity, we're going to assume that the O(k) term is literally just k, giving the recurrence
T(k+1) = 2T(k) + k.
This recurrence solves to T(k) = 2k+1 - k - 1. To see this, we can use a quick inductive argument. Specifically:
T(0) = 1 = 2 - 1 = 20+1 - 0 - 1
T(k+1) = 2T(k) + k = 2(2k - k - 1) + k
= 2k+1 - 2k - 2 + k
= 2k+1 - k - 2
= 2k+1 - (k + 1) - 1
Therefore, we get that our runtime is Θ(2n), since we can ignore the lower-order n term.
I was very surprised to see this, because this means that the quadratic work done by each recursive call does not factor into the overall runtime! I would have initially guessed that the runtime would be something like Θ(n · 2n) before doing this analysis. :-)

I believe the answer should actually be O(2^(n-1)). You can see a proof as this as well as a worst-case example here:
https://leetcode.com/problems/word-break/discuss/169383/The-Time-Complexity-of-The-Brute-Force-Method-Should-Be-O(2n)-and-Prove-It-Below

One intuitive way I think about the complexity here is, how many ways are there to add spaces in the string here or break the word here ?
for a 4 letter word:
no of ways to break at index 0-1 * no of ways to break at index 1-2 * no of ways to break at index 2-3 = 2 * 2 * 2.
2 is there to signify the two options => you break it, you don't break it
O(2^(n-1)) is the recursive complexity of the wordbreak then ;)

Related

Finding the linear recurrence (of a recursive algorithm)

I need help with finding the complexity of a recursive algorith; I know that in order to solve this I have to find the linear recurrence, then apply the Master Theorem. As of my knowledge, finding the recurrence would be straightforward when only one parameter is considered;
In this case there are two parameters (i, j). Consider the function below called on (A,1,n):
integer stuff(integer [] A, integer i, integer j){
if i ≥ j then return i – j
integer h ← 0
for integer k ← 1 to floor((j – i + 1)/3) do {
h ← h + 1
}
return stuff(A, i , i + h) + stuff(A, j – h, j) – stuff(A, i + h + 1, j – h − 1)
}
Assuming various things, I guessed the relation to be:
T(1) = k
T(n) = T(n/3) + T(n/3) + T(n/3) + 1/3*n = 3*T(n/3) + 1/3*n
I assumed that because it looks that the function is called over 3 parts of 3, of which each is one third of n; being h = O(n/3)
First call: h+i-i = h ~ n/3
Second call: j-(j-h) = h ~ n/3
Third call: j-h-1-(i+h) = j-i-2h ~ n/3 (which I only assumed)
Even though I can try to guess the relation and make sense out of it, I don't know how to formally prove it.
If my guessing is correct, how do you get to that conclusion? If not, what am I missing?
Sorry for the long question, Thanks in advance
As you return inside the for, it means all the time the function will be finished just with a constant complexity! Because all the time goes to the for loop and it return the value of the function and everything is finished and the result is ready to be returned.
Also, the proof of the recurrent relationship comes from your analysis. If you use some counting principle in Combinatorics, the final result will be proved.
Moreover, if you correct the pseudocode and put the return at the end of the function, the complexity is T(n) = 3T(n/3) + \Theta(n) (as you analyzed). Now, from the master theorem, you can say that T(n) = n log(n)).

Calculating Big O complexity of Recursive Algorithms

Somehow, I find that it is much harder to derive Big O complexities for recursive algorithms compared to iterative algorithms. Do provide some insight about how I should go about solving these 2 questions.
*assume that submethod has linear complexity
def myMethod(n)
if (n>0)
submethod(n)
myMethod(n/2)
end
end
def myMethod(k,n)
if(n>0)
submethod(k)
myMethod(k,n/2)
end
end
For your first problem, the recurrence will be:
T(n) = n + T(n/2)
T(n/2) = n/2 + T(n/4)
...
...
...
T(2) = 2 + T(1)
T(1) = 1 + T(0) // assuming 1/2 equals 0(integer division)
adding up we get:
T(n) = n + n/2 + n/4 + n/8 + ..... 1 + T(0)
= n(1 + 1/2 + 1/4 + 1/8 .....) + k // assuming k = T(0)
= n*1/(1 - 1/2) ( sum of geometric series a/(1-r) when n tends to infinity)
= 2n + k
Therefore, T(n) = O(n). Remember i have assumed n tends to infinity ,cause this is what we do in Asymptotic analysis.
For your second problem its easy to see that, we perform k primitive operations everytime till n becomes 0. This happens log(n) times. Therefore, T(n) = O(k*log(n))
All you need to do is count how many times a basic operation is executed. This is true for analysing any kind of algorithm. In your case, we will count the number of times submethod is called.
You could break-down the running time of call myMethod(n) to be 1 + myMethod(n / 2). Which you can further break down to 1 + (1 + myMethod(n / 4)). At some point you will reach the base case, in log(n)th step. That gives you an algorithm of log(n).
The second one is no different, since k is constant all the time, it will again take log(n) time, assuming submethod takes constant time regardless of its input.

Recursive Find Big O time complexity

I am learning about time complexity and I trying to figure out a relationship. My lecture notes describe a recursive find functions as :
find(array A, item I)
if(arrayEmpty(A)) return BAD;
if(item == A[0]) return GOOD;
return find(allButFirst(A), I);
useful link : https://www.youtube.com/watch?v=_cG5KZSn1LE
My notes says that the starting relationship for time complexity is as a follow:
T(n) = 1 + T(n-1) // I understand this
T(1) = 1 // only one computation, understandable
then we unroll T(n)
T(N) = 1 + 1 + T(n-2) // every recursive step 1 comparison plus recursive call
T(N) = 1 + 1 + 1 + T(n-3)
T(N) = 1 + 1 + 1 + 1 + T(n-4)
...
T(N) = (n - 1) + T(n-(n-1)) // This point I am lost how they got this generalisation
If someone can explain how the above relation was generalised to T(N) = (n - 1) + T(n-(n-1)) and perhaps with a example would be better for clarity.
For example I want to try the above relationship with some values so let's say A {1, 2, 3} and I = 3
then here are the following computation
1 + T(n-1) // {2,3}
1 + 1 + T(n-2) // {3}
1 // {3} Found
So for above we had total 3 comparison and 2 recursive calls. So I would say the relationship is T(n) = n + t(n-(n-1)) = n + t(1) = n.
In each step of the unrolled pattern, if we are k steps into the recursion (for the first step, k=1, the last step, k=n), there are k 1's.
In the expansion you posted, the last line, T(N) = (n - 1) + T(n-(n-1)) is the second-to-last step, since T(n-(n-1)) expands to T(1), so for that line, k=n-1.
So there are n-1 1's in that line, hence the (n-1) term.
Likewise, the parameter passed to T at the kth step is n minus the current step, since it's the number of steps remaining. For the first row, that's n-k = n-1, hence T(n-1). For the second-to-last step, it's n-k = n-(n-1), hence T(n-(n-1)) = T(1).

Recurrence Relation based off Pseudo Code (Time complexity)

Consider the element uniqueness problem, in which we are given a range, i, i + 1, . . . , j, of indices for an array, A, and we want to determine if the elements of this range, A[i], A[i+1], . . . , A[j], are all unique, that is, there is no repeated element in this group of array entries. Consider the following (inefficient) recursive algorithm.
public static boolean isUnique(int[] A, int start, int end) {
if (start >= end) return true; // the range is too small for repeats
// check recursively if first part of array A is unique
if (!isUnique(A,start,end-1) // there is duplicate in A[start],...,A[end-1]
return false;
// check recursively if second part of array A is unique
if (!isUnique(A,start+1,end) // there is duplicate in A[start+1],...,A[end]
return false;
return (A[start] != A[end]; // check if first and last are different
}
Let n denote the number of entries under consideration, that is, let n = end − start + 1. What is an upper is upper bound on the asymptotic running time of this code fragment for large n? Provide a brief and precise explanation.
(You lose marks if you do not explain.) To begin your explanation, you may say how many recursive calls the
algorithm will make before it terminates, and analyze the number of operations per invocation of this algorithm.
Alternatively, you may provide the recurrence characterizing the running time of this algorithm, and then solve it
using the iterative substitution technique?
This question is from a sample practise exam for an Algorithms class this is my current answer can some one please help verify if im on the right track
Answer:
The recurrence equation:
T(n) = 1 if n = 1,
T(n) = 2T(n-1) if n > 1
after solving using iterative substitution i got
2^k * T (n-k) and I solved this to O(2^(n-1)) and I simplified it O(2^n)
Your recurrence relation should be T(n) = 2T(n-1) + O(1) with T(1) = O(1). However this doesn't change the asymptotics, the solution is still T(n) = O(2^n). To see this you can expand the recurrence relation to get T(n) = O(1) + 2(O(1) + 2(O(1) + ...)) so you have T(n) = O(1) * (1 + 2 + 4 = ... + 2^n) = O(1) * (2^(n+1) - 1) = O(2^n).

Can someone help solve this recurrence relation? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 8 years ago.
Improve this question
T(n) = 2T(n/2) + 0(1)
T(n) = T(sqrt(n)) + 0(1)
In the first one I use substitution method for n, logn, etc; all gave me wrong answers.
Recurrence trees: I don't know if I can apply as the root will be a constant.
Can some one help?
Let's look at the first one. First of all, you need to know T(base case). You mentioned that it's a constant, but when you do the problem it's important that you write it down. Usually it's something like T(1) = 1. I'll use that, but you can generalize to whatever it is.
Next, find out how many times you recur (that is, the height of the recursion tree). n is your problem size, so how many times can we repeatedly divide n by 2? Mathematically speaking, what's i when n/(2^i) = 1? Figure it out, hold onto it for later.
Next, do a few substitutions, until you start to notice a pattern.
T(n) = 2(2(2T(n/2*2*2) + θ(1)) + θ(1)) + θ(1)
Ok, the pattern is that we multiply T() by 2 a bunch of times, and divide n by 2 a bunch of times. How many times? i times.
T(n) = (2^i)*T(n/(2^i)) + ...
For the big-θ terms at the end, we use a cute trick. Look above where we have a few substitutions, and ignore the T() part. We want the sum of the θ terms. Notice that they add up to (1 + 2 + 4 + ... + 2^i) * θ(1). Can you find a closed form for 1 + 2 + 4 + ... + 2^i? I'll give you that one; it's (2^i - 1). It's a good one to just memorize, but here's how you'd figure it out.
Anyway, all in all we get
T(n) = (2^i) * T(n/(2^i)) + (2^i - 1) * θ(1)
If you solved for i earlier, then you know that i = log_2(n). Plug that in, do some algebra, and you get down to
T(n) = n*T(1) + (n - 1)*θ(1). T(1) = 1. So T(n) = n + (n - 1)*θ(1). Which is n times a constant, plus a constant, plus n. We drop lower order terms and constants, so it's θ(n).
Prasoon Saurav is right about using the master method, but it's important that you know what the recurrence relation is saying. The things to ask are, how much work do I do at each step, and what is the number of steps for an input of size n?
Use Master Theorem to solve such recurrence relations.
Let a be an integer greater than or equal to 1 and b be a real number greater than
1. Let c be a positive real number and
d a nonnegative real number. Given a recurrence of the form
T (n) = a T(n/b) + nc .. if n > 1
T(n) = d .. if n = 1
then for n a power of b,
if logb a < c, T (n) = Θ(nc),
if logb a = c, T (n) = Θ(nc log n),
if logb a > c, T (n) = Θ(nlogb a).
1) T(n) = 2T(n/2) + 0(1)
In this case
a = b = 2;
logb a = 1; c = 0 (since nc =1 => c= 0)
So Case (3) is applicable. So T(n) = Θ(n) :)
2) T(n) = T(sqrt(n)) + 0(1)
Let m = log2 n;
=> T(2m) = T( 2m / 2 ) + 0(1)
Now renaming K(m) = T(2m) => K(m) = K(m/2) + 0(1)
Apply Case (2).
For part 1, you can use Master Theorem as #Prasoon Saurav suggested.
For part 2, just expand the recurrence:
T(n) = T(n ^ 1/2) + O(1) // sqrt(n) = n ^ 1/2
= T(n ^ 1/4) + O(1) + O(1) // sqrt(sqrt(n)) = n ^ 1/4
etc.
The series will continue to k terms until n ^ 1/(2^k) <= 1, i.e. 2^k = log n or k = log log n. That gives T(n) = k * O(1) = O(log log n).
Let's look at the first recurrence, T(n) = 2T(n/2) + 1. The n/2 is our clue here: each nested term's parameter is half that of its parent. Therefore, if we start with n = 2^k then we will have k terms in our expansion, each adding 1 to the total, before we hit our base case, T(0). Hence, assuming T(0) = 1, we can say T(2^k) = k + 1. Now, since n = 2^k we must have k = log_2(n). Therefore T(n) = log_2(n) + 1.
We can apply the same trick to your second recurrence, T(n) = T(n^0.5) + 1. If we start with n = 2^2^k we will have k terms in our expansion, each adding 1 to the total. Assuming T(0) = 1, we must have T(2^2^k) = k + 1. Since n = 2^2^k we must have k = log_2(log_2(n)), hence T(n) = log_2(log_2(n)) + 1.
Recurrence relations and recursive functions as well should be solved by starting at f(1). In case 1, T(1) = 1; T(2) = 3; T(4) = 7; T(8) = 15; It's clear that T(n) = 2 * n -1, which in O notation is O(n).
In second case T(1) = 1; T(2) = 2; T(4) = 3; T(16) = 4; T(256) = 5; T(256 * 256) =6; It will take little time to find out that T(n) = log(log(n)) + 1 where log is in base 2. Clearly this is O(log(log(n)) relation.
Most of the time the best way to deal with recurrence is to draw the recurrence tree and carefully handle the base case.
However here I will give you slight hint to solve using substitution method.
In recurrence first try substitution n = 2^k
In recurrence second try substitution n = 2^2^k

Resources