Is there a way to reduce the time complexity of the program? - c++

Assume there are n prisoners standing in a circle. The first prisoner has a knife with which he kills the second prisoner and passes on the knife to the third person who kills the fourth prisoner and passes the knife to the fifth prisoner.
This cycle is repeated till only one prisoner is left. Note that the prisoners are standing in a circle, thus the first prisoner is next to the nth prisoner. Return the index of the last standing prisoner.
I tried implementing the solution using a circular linked list. Here's my code
The structure of the circular linked list is:-
struct Node
{
int Data;
Node *Next;
};
Node *Head = NULL;
Here are my deleteByAddress() and main() functions:-
inline void deleteByAddress(Node *delNode)
{
Node *n = Head;
if(Head == delNode)
{
while(n -> Next != Head)
{
n = n -> Next;
}
n -> Next = Head -> Next;
free(Head);
Head = n -> Next;
return ;
}
while(n -> Next != delNode)
{
n = n -> Next;
}
n -> Next = delNode -> Next;
delete delNode;
}
int main(void)
{
for(int i = 1 ; i <= 100 ; i++)
insertAtEnd(i);
Node *n = Head;
while(Head -> Next != Head)
{
deleteByAddress(n -> Next);
n = n -> Next;
}
cout << Head -> Data;
return 0;
}
The above code works perfectly and produces the desired output for n = 100, which is 73.
Is there any way we can reduce the time complexity or use a more efficient data structure to implement the same question.

This is known as the Josephus problem. As the Wikipedia page shows and others have noted, there is a formula for when k is 2. The general recurrence is
// zero-based Josephus
function g(n, k){
if (n == 1)
return 0
return (g(n - 1, k) + k) % n
}
console.log(g(100, 2) + 1)

This can easily be solved with O(1) complexity using the following:
last = (num - pow(2, int(log(num)/log(2)))) * 2 + 1
for example for num = 100 :
last = (100 - pow(2, int(log(100)/log(2)))) * 2 + 1 = 73
And if you have log2() function, you may replace a bit ugly log(num)/log(2) which basically takes a logarithm with the base 2.

Use 1 loop. You can grab, at every iteration, the current one's next, then set current to the next ones next and then delete the next one.
This assumes all the data is set up before hand and ignores the rewriting of the next variable when you hit the bounds.

The trick to reduce time complexity is to come up with more clever algorithms than brute-forcing it by simulation.
Here, as so often, key is obviously to solve the math. The first loop, for example, kills everybody with i%2=1 (assuming 0 based indexing), the second everybody with i%4=(n+1)%2*2 or so etc. - I'd be looking for a closed form to directly compute the survivor. It will likely boil down to a few bit manipulations yielding a O(log n) algorithm that is almost instant in practise because of all running completely in CPU registers with not even L1 cache accesses.

For such a simple processing the list manipulation and memory allocation is going to dominate the computation, you could use just a single array where you have an index to the first alive and each element is the index of next alive.
That said you could indeed search for a formula that avoids doing the loops... for example if the number of prisoners is even then after the first "loop" you end up with half of the prisoners and the knife back in the hands of first one. This means that the index of the surviving prisoner when n is even is
f(n) = 2 * f(n / 2) # when n is even
in case n is odd things are a bit more complex... after the first loop you will end up with (n + 1)/2 prisoners, but the knife in the hand of last one so some modulo arithmetic is needed because you need to "adjust" the result of the recursive call f((n + 1)/2).

The method to reduce time complextiy is, as in most cases that a challenge fails for out-of-time reasons, to not simulate and use math instead. With luck it turns into a one-liner.
The algorithm can be sped up very much, if you change to:
Note that for a total number of prisoners which is a power of two, always index 0 will survive.
For other cases:
determine the highest power of two which is lower or equal to the number of prisoners
determine R, the rest when reducing the number of prisoners by that power of two
the prisoner who survives in the end will be the one who gets the knife after that number of prisoners has been killed
Trying to find out which prisoner that is.
Case of 5 prisoners (1 higher than 22, R=1):
01234
Deaths 1: x x
Deaths 2:x x
last : O
Case of 6 (R=2):
012345
Deaths 1: x x x
Deaths 2:x x (index 4 kills index 0 after index 2 was killed by index 0)
last : O
Case of 7 (R=3):
0123456
Deaths 1:xx x x (index 6 kills index 0 after index 5 was killed by index 2)
Deaths 2: x x (index 6 kills index 2 after index 4 was killed by index 2)
last : O
Case of 8 is the next power of two, index 0 survives.
In the end, the final survivor is always the one at index 2*R.
Hence, instead of simulating, you just need to determine R.
That should be possible at worst in a time complexity of order of logarithm to base 2 of total number.

Related

How to erase elements more efficiently from a vector or set?

Problem statement:
Input:
First two inputs are integers n and m. n is the number of knights fighting in the tournament (2 <= n <= 100000, 1 <= m <= n-1). m is the number of battles that will take place.
The next line contains n power levels.
The next m lines contain two integers l and r, indicating the range of knight positions to compete in the ith battle.
After each battle, all nights apart from the one with the highest power level will be eliminated.
The range for each battle is given in terms of the new positions of the knights, not the original positions.
Output:
Output m lines, the ith line containing the original positions (indices) of the knights from that battle. Each line is in ascending order.
Sample Input:
8 4
1 0 5 6 2 3 7 4
1 3
2 4
1 3
0 1
Sample Output:
1 2
4 5
3 7
0
Here is a visualisation of this process.
1 2
[(1,0),(0,1),(5,2),(6,3),(2,4),(3,5),(7,6),(4,7)]
-----------------
4 5
[(1,0),(6,3),(2,4),(3,5),(7,6),(4,7)]
-----------------
3 7
[(1,0),(6,3),(7,6),(4,7)]
-----------------
0
[(1,0),(7,6)]
-----------
[(7,6)]
I have solved this problem. My program produces the correct output, however, it is O(n*m) = O(n^2). I believe that if I erase knights more efficiently from the vector, efficiency can be increased. Would it be more efficient to erase elements using a set? I.e. erase contiguous segments rather that individual knights. Is there an alternative way to do this that is more efficient?
#define INPUT1(x) scanf("%d", &x)
#define INPUT2(x, y) scanf("%d%d", &x, &y)
#define OUTPUT1(x) printf("%d\n", x);
int main(int argc, char const *argv[]) {
int n, m;
INPUT2(n, m);
vector< pair<int,int> > knights(n);
for (int i = 0; i < n; i++) {
int power;
INPUT(power);
knights[i] = make_pair(power, i);
}
while(m--) {
int l, r;
INPUT2(l, r);
int max_in_range = knights[l].first;
for (int i = l+1; i <= r; i++) if (knights[i].first > max_in_range) {
max_in_range = knights[i].first;
}
int offset = l;
int range = r-l+1;
while (range--) {
if (knights[offset].first != max_in_range) {
OUTPUT1(knights[offset].second));
knights.erase(knights.begin()+offset);
}
else offset++;
}
printf("\n");
}
}
Well, removing from vector wouldn't be efficient for sure. Removing from set, or unordered set would be more effective (use iterators instead of indexes).
Yet the problem will still remain O(n^2), because you have two nested whiles running n*m times.
--EDIT--
I believe I understand the question now :)
First let's calculate the complexity of your code above. Your worst case would be the case that max range in all battles is 1 (two nights for each battle) and the battles are not ordered with respect to the position. Which means you have m battles (in this case m = n-1 ~= O(n))
The first while loop runs n times
For runs for once every time which makes it n*1 = n in total
The second while loop runs once every time which makes it n again.
Deleting from vector means n-1 shifts that makes it O(n).
Thus with the complexity of the vector total complexity is O(n^2)
First of all, you don't really need the inner for loop. Take the first knight as the max in range, compare the rest in the range one-by-one and remove the defeated ones.
Now, i believe it can be done in O(nlogn) with using std::map. The key to the map is the position and the value is the level of the knight.
Before proceeding, finding and removing an element in map is logarithmic, iterating is constant.
Finally, your code should look like:
while(m--) // n times
strongest = map.find(first_position); // find is log(n) --> n*log(n)
for (opponent = next of strongest; // this will run 1 times, since every range is 1
opponent in range;
opponent = next opponent) // iterating is constant
// removing from map is log(n) --> n * 1 * log(n)
if strongest < opponent
remove strongest, opponent is the new strongest
else
remove opponent, (be careful to remove it after iterating to next)
Ok, now the upper bound would be O(2*nlogn) = O(nlogn). If the ranges increases, that makes the run time of upper loop decrease but increases the number of remove operations. I'm sure the upper bound won't change, let's make it a homework for you to calculate :)
A solution with a treap is pretty straightforward.
For each query, you need to split the treap by implicit key to obtain the subtree that corresponds to the [l, r] range (it takes O(log n) time).
After that, you can iterate over the subtree and find the knight with the maximum strength. After that, you just need to merge the [0, l) and [r + 1, end) parts of the treap with the node that corresponds to this knight.
It's clear that all parts of the solution except for the subtree traversal and printing work in O(log n) time per query. However, each operation reinserts only one knight and erase the rest from the range, so the size of the output (and the sum of sizes of subtrees) is linear in n. So the total time complexity is O(n log n).
I don't think you can solve with standard stl containers because there'no standard container that supports getting an iterator by index quickly and removing arbitrary elements.

Why time complexity of following code is O(n^2)?

void level_order_recursive(struct node *t , int h) //'h' is height of my binary tree
{ //'t' is address of root node
for(int i = 0 ; i <= h ; i++)
{
print_level(t , i);
}
}
After print_level() is called everytime , I think recursive function is called (2^i) times . So 2^0 + 2^1 + 2^2 ....2^h should give time complexity of O(2^n).Where am I going wrong ?
void print_level(struct node * t , int i)
{
if( i == 0)
cout << t -> data <<" ";
else
{
if(t -> left != NULL)
print_level(t -> left , i - 1); //recursive call
if(t -> right != NULL)
print_level(t -> right , i - 1); //recursive call
}
}
You are confusing h and n. h is the height of the tree. n is apparently the number of elements in the tree. So print_level takes worst case O ($2^i), but that is also just n.
The worst case happens when you have a degenerate tree, where each node has only one successor. In that case you have n nodes, but the height of the tree is also h = n. Each call to print_level takes i steps in that case, and summing up i from 1 to h = n gives O ($n^2).
You always start at the root of the tree t and increase the level by one each time (i) until you reach the height of the tree h.
You said it is a binary tree, but you did not mention any property, e.g. balanced or so. So I assume it can be an unbalanced binary tree and thus the height of the tree in worst case can be h = n where n is the number of nodes (that is a completely unbalanced tree that looks like a list actually).
So this means that level_order_recursive loops n times. I.e. the worst case is that the tree has n levels.
print_level receives the root node and the level to print. And it calls itself recursively until it reaches the level and prints out that level.I.e. it loops i times (a recursive call decreases i by one each time).
So you have 1 + 2 + 3 + ... + h iterations. And since h = n you get 1 + 2 + 3 ... + n steps. This is (n * (n+1))/2 (Gaussian sum formula) which is in O(n^2).
If you can assure that the tree is balanced than you would improve the worst case scenario, because the height would be h = ld(n) where ld denotes the binary logarithm.
Based on this or that, pages 3 and 4, binary search algorithm, which resembles our case, has a time complexity of T(n) = T(n/2) + c.
Except that, both left and right sub-trees are browsed, hence the 2T(n/2) in the formula below, since this is a traversal algorithm, rather than a search one.
Here, I will comply to the question and use 'h' instead of 'n'.
Using recurrence relation, you get the following proof:
In the worst case the time complexity will be O(n^2) but cannot be 2^n as time complexity for each level will be-> O(n) + O(n-1) + O(n-2) + ... + O(1) which is at worst O(n^2).

Interview: Summing numbers in two linked lists

During an interview yesterday, I was asked how I would go about summing the values of two singly linked lists that contained digits. They also said the lists could be unequal lengths.
I asked if the list was stored backwards, as that's how I learned about it at uni, but they said no, it was stored forward. They also said I couldn't simply reverse the lists, add then, then reverse it to get it forward again because that option required too much processing. This sort of solution is all I've been able to find online.
I was unable to give an answer, even after they hinted that I should be doing this with a recursive function.
Can anyone help me out with what the solution would have been. This was for a C++ job and I'm hoping that if I ever get called back and I'm able to explain I researched the solution, they might see that as a good sign. Thank you.
For those confused about how the summation is supposed to work, it was presented in this way.
List 1: 1->2->9
List 2: 1->3
So since the numbers are stored forward, I would need to begin by adding the 9 and 3 (end of both lists). Then take the 1 carry and do 1 + 2 + 1. Etc.
You count the length of both lists. You pad at the beginning the shorter list with a number of 0 digits so that they are equal in length. Now you pad both numbers with an extra 0 (it will be used by the carry of the first digits. So that it's possible that 9 + 1 = 10).
You create a third linked list of length equal to the previous two.
Now you do a class like this:
class Digit
{
public:
Digit *Next;
int Dt;
}
and a function like this:
int Sum(const Digit* left, const Digit* right, Digit* runningTotal)
{
int carry = 0;
if (left->Next != NULL)
{
carry = Sum(left->Next, right->Next, runningTotal->Next);
}
carry += left->Dt + right->Dt;
runningTotal->Dt = carry % 10;
carry /= 10;
return carry;
}
This is "version 0".
In "version 1" you remove the extra padding for the last carry and you add it only if needed.
In "version 2" you remove unnecessary "0" digits from the front of the linked lists.
In "version 3" you create the runningTotal linked list directly in Sum. You give to the first level Sum only the "Head" of the Running Total.
In "version 4" instead of padding the shorter LL, you pass a parameter on the number of digits to skip from the longest LL (this is the most difficult passage).
There is another possibility, much more complex, but that doesn't require to pre-count the length of the lists. It uses two recursive functions:
The first recursive function simply traverses left and right while both are present. If both finishes at the same time then you can simply roll-back as in the previous example.
If one of them finishes before the other, then you use another recursive function like this (the initial value of *extraDigits is 1):
void SaveRemainingDigits(const Digit *remaining, int *extraDigits, int **buffer)
{
int currentDigit = *extraDigits - 1;
*extraDigits = *extraDigits + 1;
if (remaining->Next)
{
SaveRemainingDigits(remaining->Next, extraDigits, buffer);
}
else
{
*buffer = (int*)malloc(sizeof(int) * extraDigits);
}
(*buffer)[currentDigit] = remaining->Dt;
}
when this function finally returns, we have a scratchpad from where to extract the digits and the length of the scratchpad
The innermost level of our first recursive function has now to sum its current digit of the shortest linked list with the last digit of the scratchpad and put the current digit of the longest linked list in the scratchpad in place of the digit just used. Now you unroll your recursive function and you use the scratchpad as a circular array. When you finish unrolling, then you add elements to the runningTotal linked list taking them directly from the scratchpad.
As I've said, it's a little complex, but in 1-2 hours I could write it down as a program.
An example (without carry)
1 2 3 4
6 5
you recurse the first two elements. So you have
1-6 (in the first level)
2-5 (in the second level)
Now you see that the second list is finished and you use the second function.
3 (extraDigit enters as 0, is modified to 1. currentDigit = 0)
4 (extraDigit enters as 1, is modified to 2. currentDigit = 1.
malloc of 2 elements,
buffer[currentDigit] = 4 => buffer[1] = 4)
unroll and we return to the previous row
3 (currentDigit = 0
buffer[currentDigit] = 3 => buffer[0] = 3)
Now we return to the previous function
2-5 (in the second level,
with a lengthBuffer == 2,
we set index = length(buffer) - 1
currentDigitTotal = 5 + buffer[index] => currentDigitTotal = 5 + 4
buffer[index] = 2 => buffer[1] = 2;
index = (index - 1 + lengthBuffer) % lengthBuffer => index = 0
1-6 (in the first level,
with a lengthBuffer == 2,
index = 0,
currentDigitTotal = 6 + buffer[index] => currentDigitTotal = 6 + 3
buffer[index] = 1 => buffer[0] = 1;
index = (index - 1 + lengthBuffer) % lengthBuffer => index = 1
now we exited the recursive function.
In an external function we see that we have a buffer.
We add its elements to the head of the total.
Our Linked list now is 9-9 and our buffer is 1,2 with index 1
for (int i = 0; i < lengthBuffer; i++)
{
runningTotal.AddHead(buffer(index));
index = (index - 1 + lengthBuffer) % lengthBuffer
}
I will approach this problem in something like this
Let's suppose the 2 lists are :
1->2->7->6->4->3 and
5->7->2
The sum is 1->2->7 + Sum(6->4->3, 5->7->2)
Now we make a function that take 2 lists of same size and returns their sum
which will be something like
list1->val + list2->val + Sum(list1->next, list2->next)
with base case if(list1->next == NULL) return list1->val+list2->val;
Note :: we can handle the carry in next pass easily or you can handle that in our sum function itself
So after all this our ans will be 1->2->7->11->11->5
then recursively do %10 and take carry and add it to previous value.
so final ans will be 1->2->8->2->1->5
I would have created a node like *head or *tail to store the address of the node that I started from, then iterate through the list making sure im not back at my start point. This doesn't require to to have to count the length of each, which sounds inefficient.
As for the recursiveness just do this check at the top of the function and return (node->value + myfunct(node->prev)); It'd be more efficient given you're doing the math once.
The lists "1, 2, 9" and "1, 3" each represent the numbers "129" and "13", in which case the sum is "142".
Using recursion
Compute the length of each list.
If the lengths differ, pad the shortest with zeroes at the beggining.
Iterate over the lists recursively, returning: a) the carry number if any, or zero otherwise, and b) the tail of the list.
In pseudocode:
def sum_lists_rec(a, b, start_a, start_b, length_a, length_b):
"""Returns a pair of two elements: carry and the tail of the list."""
if the end of the lists:
return (0, empty_list)
result = sum_lists_rec(a+1, b+1, start_a+1, start_b+1, length_a, length_b)
carry = (a[0] + b[0] + result[0]) / 10
digit = (a[0] + b[0] + result[0]) % 10
return (carry, [digit] ++ result[1])
def sum_lists1(a, b):
length_a = length(a)
length_b = length(b)
if length_a < length_b:
a = [0, 0, ..., (length_b - length_a)] ++ a
else if length_b < length_a:
b = [0, 0, ..., (length_a - length_b)] ++ b
result = sum_lists_rec(a, b, length_a, length_b, 0, 0)
if result[0] != 0:
return [result[0]] ++ result[1]
else:
return result[1]
As an alternative, you can use a stack:
Compute the length of each list.
If the lengths differ, pad the shortest with zeroes at the beggining.
Push each digit of both lists on the stack.
Pop the stack until is empty, creating the new list.

How to find maximum of each subarray of some fixed given length in a given array

We are given an array of n elements and an integer k. Suppose that we want to slide a window of length k across the array, reporting the largest value contained in each window. For example, given the array
15 10 9 16 20 14 13
Given a window of length 4, we would output
[15 10 9 16] 20 14 13 ---> Output 16
15 [10 9 16 20] 14 13 ---> Output 20
15 10 [ 9 16 20 14] 13 ---> Output 20
15 10 9 [16 20 14 13] ---> Output 20
So the result would be
16 20 20 20
I was approaching the problem by keeping track of the maximum element of the window at each point, but ran into a problem when the largest element gets slid out of the window. At that point, I couldn't think of a fast way to figure out what the largest remaining element is.
Does anyone know of an efficient algorithm for solving this problem?
This older question discusses how to build a queue data structure supporting insert, dequeue, and find-min all in O(1) time. Note that this is not a standard priority queue, but instead is a queue in which at any point you can find the value of the smallest element it contains in O(1) time. You could easily modify this structure to support find-max in O(1) instead of find-min, since that's more relevant to this particular problem.
Using this structure, you can solve this problem in O(n) time as follows:
Enqueue the first k elements of the array into the special queue.
For each element in the rest of the array:
Use the queue's find-max operation to report the largest element of the current subrange.
Dequeue an element from the queue, leaving the last k-1 elements of the old range in place.
Enqueue the next element from the sequence, causing the queue to now hold the next k-element subrange of the sequence.
This takes a total of O(n) time, since you visit each array element once, enqueuing and dequeuing each at most once, and calling find-max exactly n-k times. I think this is pretty cool, since the complexity is independent of k, which doesn't initially seem like it necessarily should be possible.
Hope this helps! And thanks for asking a cool question!
You can keep a Binary Search Tree of the current elements, for example, save them as value-occurrence pairs. Other than that, you sliding window algorithm should be good enough.
This way, select maximum (the max element in the subsection) will cost O(logL) time, L being the length of the current subsection; add new would also be O(logL). TO delete the oldest one, just search the value and decrements the count by 1, if the count goes to 0 delete it.
So the total time will be O(NlogL), N being the length of the array.
The best I can come up with quickly is O(n log m).
You can get that by dynamic programming.
In the first pass you find max for every element the maximum from the element itself and the next.
Now you have n maximums (window size = 2).
Now you can find on this array the maximum from every element and the overnext in this array (gives you for each element the maximum for the next 4, ie window size = 4).
Then you can do it again, and again (and every time the window size doubles).
As one clearly sees the window size grows exponentially.
Therefor the runtime is O(n log m). The implementation is a bit tricky, because you must consider the corner and special cases (esp. when the windows size should not be a power of two), but they didnt influence the asymptotic runtime.
You could proceed like a tabu search :
Loop through the list and get the max of the 4 first ith element.
Then on the next step just check if the i+1th element is superior to the max of the previous elements
if i+1>=previous max then new max = i+1 reinialise tabu
if i+1< previous max then if the previous max was found less than N
step ago keep the previous (here is the tabu )
if i+1< preivous max and the previous max is tabu then take the new
max of the 4 i+1th elements.
I'm not sure it's clear but tell me if you have any question.
below is a code in python to test it.
l=[15,10,9,16,20,14,13,11,12]
N=4
res=[-1] #initialise res
tabu=1 #initialise tabu
for k in range(0,len(l)):
#if the previous element res[-1] is higher than l[k] and not tabu then keep it
#if the previous is tabu and higher than l[k] make a new search without it
#if the previous is smaller than l[k] take the new max =l[k]
if l[k]<res[-1] and tabu<N:
tabu+=1
res.append(res[-1])
elif l[k] < res[-1] and tabu == N:
newMax=max(l[k-N+1:k+1])
res.append(newMax)
tabu=N-l[k-N+1:k+1].index(newMax) #the tabu is initialized depending on the position of the newmaximum
elif l[k] >= res[-1]:
tabu=1
res.append(l[k])
print res[N:] #take of the N first element
Complexity:
I updated the code thx to flolo and the complexity. it's not anymore O(N) but O(M*N)
The worst case is when you need to recalculate a maximum at each step of the loop. i e a strictly decreasing list for example.
at each step of the loop you need to recalculate the max of M elements
then the overall complexity is O(M*N)
You can achieve O(n) complexity by using Double-ended queue.
Here is C# implementation
public static void printKMax(int[] arr, int n, int k)
{
Deque<int> qi = new Deque<int>();
int i;
for (i=0;i< k; i++) // first window of the array
{
while ((qi.Count > 0) && (arr[i] >= arr[qi.PeekBack()]))
{
qi.PopBack();
}
qi.PushBack(i);
}
for(i=k ;i< n; ++i)
{
Console.WriteLine(arr[qi.PeekFront()]); // the front item is the largest element in previous window.
while (qi.Count > 0 && qi.PeekFront() <= i - k) // this is where the comparison is happening!
{
qi.PopFront(); //now it's out of its window k
}
while(qi.Count>0 && arr[i]>=arr[qi.PeekBack()]) // repeat
{
qi.PopBack();
}
qi.PushBack(i);
}
Console.WriteLine(arr[qi.PeekFront()]);
}
Please review my code. According to me I think the Time Complexity for this algorithm is
O(l) + O(n)
for (int i = 0; i< l;i++){
oldHighest += arraylist[i];
}
int kr = FindMaxSumSubArray(arraylist, startIndex, lastIndex);
public static int FindMaxSumSubArray(int[] arraylist, int startIndex, int lastIndex){
int k = (startIndex + lastIndex)/2;
k = k - startIndex;
lastIndex = lastIndex - startIndex;
if(arraylist.length == 1){
if(lcount<l){
highestSum += arraylist[0];
lcount++;
}
else if (lcount == l){
if(highestSum >= oldHighest){
oldHighest = highestSum;
result = count - l + 1;
}
highestSum = 0;
highestSum += arraylist[0];
lcount = 1;
}
count++;
return result;
}
FindMaxSumSubArray(Arrays.copyOfRange(arraylist, 0, k+1), 0, k);
FindMaxSumSubArray(Arrays.copyOfRange(arraylist, k+1, lastIndex+1), k+1, lastIndex);
return result;
}
I don't understand if this is better off to do in recursion or just linearly?

How Recursion Works Inside a For Loop

I am new to recursion and trying to understand this code snippet. I'm studying for an exam, and this is a "reviewer" I found from Standford' CIS Education Library (From Binary Trees by Nick Parlante).
I understand the concept, but when we're recursing INSIDE THE LOOP, it all blows! Please help me. Thank you.
countTrees() Solution (C/C++)
/*
For the key values 1...numKeys, how many structurally unique
binary search trees are possible that store those keys.
Strategy: consider that each value could be the root.
Recursively find the size of the left and right subtrees.
*/
int countTrees(int numKeys) {
if (numKeys <=1) {
return(1);
}
// there will be one value at the root, with whatever remains
// on the left and right each forming their own subtrees.
// Iterate through all the values that could be the root...
int sum = 0;
int left, right, root;
for (root=1; root<=numKeys; root++) {
left = countTrees(root - 1);
right = countTrees(numKeys - root);
// number of possible trees with this root == left*right
sum += left*right;
}
return(sum);
}
Imagine the loop being put "on pause" while you go in to the function call.
Just because the function happens to be a recursive call, it works the same as any function you call within a loop.
The new recursive call starts its for loop and again, pauses while calling the functions again, and so on.
For recursion, it's helpful to picture the call stack structure in your mind.
If a recursion sits inside a loop, the structure resembles (almost) a N-ary tree.
The loop controls horizontally how many branches at generated while the recursion decides the height of the tree.
The tree is generated along one specific branch until it reaches the leaf (base condition) then expand horizontally to obtain other leaves and return the previous height and repeat.
I find this perspective generally a good way of thinking.
Look at it this way: There's 3 possible cases for the initial call:
numKeys = 0
numKeys = 1
numKeys > 1
The 0 and 1 cases are simple - the function simply returns 1 and you're done. For numkeys 2, you end up with:
sum = 0
loop(root = 1 -> 2)
root = 1:
left = countTrees(1 - 1) -> countTrees(0) -> 1
right = countTrees(2 - 1) -> countTrees(1) -> 1
sum = sum + 1*1 = 0 + 1 = 1
root = 2:
left = countTrees(2 - 1) -> countTrees(1) -> 1
right = countTrees(2 - 2) -> countTrees(0) -> 1
sum = sum + 1*1 = 1 + 1 = 2
output: 2
for numKeys = 3:
sum = 0
loop(root = 1 -> 3):
root = 1:
left = countTrees(1 - 1) -> countTrees(0) -> 1
right = countTrees(3 - 1) -> countTrees(2) -> 2
sum = sum + 1*2 = 0 + 2 = 2
root = 2:
left = countTrees(2 - 1) -> countTrees(1) -> 1
right = countTrees(3 - 2) -> countTrees(1) -> 1
sum = sum + 1*1 = 2 + 1 = 3
root = 3:
left = countTrees(3 - 1) -> countTrees(2) -> 2
right = countTrees(3 - 3) -> countTrees(0) -> 1
sum = sum + 2*1 = 3 + 2 = 5
output 5
and so on. This function is most likely O(n^2), since for every n keys, you're running 2*n-1 recursive calls, meaning its runtime will grow very quickly.
Just to remember that all the local variables, such as numKeys, sum, left, right, root are in the stack memory. When you go to the n-th depth of the recursive function , there will be n copies of these local variables. When it finishes executing one depth, one copy of these variable will be popped up from the stack.
In this way, you will understand that, the next-level depth will NOT affect the current-level depth local variables (UNLESS you are using references, but we are NOT in this particular problem).
For this particular problem, time-complexity should be carefully paid attention to. Here are my solutions:
/* Q: For the key values 1...n, how many structurally unique binary search
trees (BST) are possible that store those keys.
Strategy: consider that each value could be the root. Recursively
find the size of the left and right subtrees.
http://stackoverflow.com/questions/4795527/
how-recursion-works-inside-a-for-loop */
/* A: It seems that it's the Catalan numbers:
http://en.wikipedia.org/wiki/Catalan_number */
#include <iostream>
#include <vector>
using namespace std;
// Time Complexity: ~O(2^n)
int CountBST(int n)
{
if (n <= 1)
return 1;
int c = 0;
for (int i = 0; i < n; ++i)
{
int lc = CountBST(i);
int rc = CountBST(n-1-i);
c += lc*rc;
}
return c;
}
// Time Complexity: O(n^2)
int CountBST_DP(int n)
{
vector<int> v(n+1, 0);
v[0] = 1;
for (int k = 1; k <= n; ++k)
{
for (int i = 0; i < k; ++i)
v[k] += v[i]*v[k-1-i];
}
return v[n];
}
/* Catalan numbers:
C(n, 2n)
f(n) = --------
(n+1)
2*(2n+1)
f(n+1) = -------- * f(n)
(n+2)
Time Complexity: O(n)
Space Complexity: O(n) - but can be easily reduced to O(1). */
int CountBST_Math(int n)
{
vector<int> v(n+1, 0);
v[0] = 1;
for (int k = 0; k < n; ++k)
v[k+1] = v[k]*2*(2*k+1)/(k+2);
return v[n];
}
int main()
{
for (int n = 1; n <= 10; ++n)
cout << CountBST(n) << '\t' << CountBST_DP(n) <<
'\t' << CountBST_Math(n) << endl;
return 0;
}
/* Output:
1 1 1
2 2 2
5 5 5
14 14 14
42 42 42
132 132 132
429 429 429
1430 1430 1430
4862 4862 4862
16796 16796 16796
*/
You can think of it from the base case, working upward.
So, for base case you have 1 (or less) nodes. There is only 1 structurally unique tree that is possible with 1 node -- that is the node itself. So, if numKeys is less than or equals to 1, just return 1.
Now suppose you have more than 1 key. Well, then one of those keys is the root, some items are in the left branch and some items are in the right branch.
How big are those left and right branches? Well it depends on what is the root element. Since you need to consider the total amount of possible trees, we have to consider all configurations (all possible root values) -- so we iterate over all possible values.
For each iteration i, we know that i is at the root, i - 1 nodes are on the left branch and numKeys - i nodes are on the right branch. But, of course, we already have a function that counts the total number of tree configurations given the number of nodes! It's the function we're writing. So, recursive call the function to get the number of possible tree configurations of the left and right subtrees. The total number of trees possible with i at the root is then the product of those two numbers (for each configuration of the left subtree, all possible right subtrees can happen).
After you sum it all up, you're done.
So, if you kind of lay it out there's nothing special with calling the function recursively from within a loop -- it's just a tool that we need for our algorithm. I would also recommend (as Grammin did) to run this through a debugger and see what is going on at each step.
Each call has its own variable space, as one would expect. The complexity comes from the fact that the execution of the function is "interrupted" in order to execute -again- the same function.
This code:
for (root=1; root<=numKeys; root++) {
left = countTrees(root - 1);
right = countTrees(numKeys - root);
// number of possible trees with this root == left*right
sum += left*right;
}
Could be rewritten this way in Plain C:
root = 1;
Loop:
if ( !( root <= numkeys ) ) {
goto EndLoop;
}
left = countTrees( root -1 );
right = countTrees ( numkeys - root );
sum += left * right
++root;
goto Loop;
EndLoop:
// more things...
It is actually translated by the compiler to something like that, but in assembler. As you can see the loop is controled by a pair of variables, numkeys and root, and their values are not modified because of the execution of another instance of the same procedure. When the callee returns, the caller resumes the execution, with the same values for all values it had before the recursive call.
IMO, key element here is to understand function call frames, call stack, and how they work together.
In your example, you have bunch of local variables which are initialised but not finalised in the first call. It's important to observe those local variables to understand the whole idea. At each call, the local variables are updated and finally returned in a backwards manner (most likely it's stored in a register before each function call frame is popped off from the stack) up until it's added to the initial function call's sum variable.
The important distinction here is - where to return. If you need accumulated sum value like in your example, you cannot return inside the function which would cause to early-return/exit. However, if you depend on a value to be in a certain state, then you can check if this state is hit inside the for loop and return immediately without going all the way up.