Interview: Summing numbers in two linked lists - c++

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.

Related

Perfect sum problem with fixed subset size

I am looking for a least time-complex algorithm that would solve a variant of the perfect sum problem (initially: finding all variable size subset combinations from an array [*] of integers of size n that sum to a specific number x) where the subset combination size is of a fixed size k and return the possible combinations without direct and also indirect (when there's a combination containing the exact same elements from another in another order) duplicates.
I'm aware this problem is NP-hard, so I am not expecting a perfect general solution but something that could at least run in a reasonable time in my case, with n close to 1000 and k around 10
Things I have tried so far:
Finding a combination, then doing successive modifications on it and its modifications
Let's assume I have an array such as:
s = [1,2,3,3,4,5,6,9]
So I have n = 8, and I'd like x = 10 for k = 3
I found thanks to some obscure method (bruteforce?) a subset [3,3,4]
From this subset I'm finding other possible combinations by taking two elements out of it and replacing them with other elements that sum the same, i.e. (3, 3) can be replaced by (1, 5) since both got the same sum and the replacing numbers are not already in use. So I obtain another subset [1,5,4], then I repeat the process for all the obtained subsets... indefinitely?
The main issue as suggested here is that it's hard to determine when it's done and this method is rather chaotic. I imagined some variants of this method but they really are work in progress
Iterating through the set to list all k long combinations that sum to x
Pretty self explanatory. This is a naive method that do not work well in my case since I have a pretty large n and a k that is not small enough to avoid a catastrophically big number of combinations (the magnitude of the number of combinations is 10^27!)
I experimented several mechanism related to setting an area of research instead of stupidly iterating through all possibilities, but it's rather complicated and still work in progress
What would you suggest? (Snippets can be in any language, but I prefer C++)
[*] To clear the doubt about whether or not the base collection can contain duplicates, I used the term "array" instead of "set" to be more precise. The collection can contain duplicate integers in my case and quite much, with 70 different integers for 1000 elements (counts rounded), for example
With reasonable sum limit this problem might be solved using extension of dynamic programming approach for subset sum problem or coin change problem with predetermined number of coins. Note that we can count all variants in pseudopolynomial time O(x*n), but output size might grow exponentially, so generation of all variants might be a problem.
Make 3d array, list or vector with outer dimension x-1 for example: A[][][]. Every element A[p] of this list contains list of possible subsets with sum p.
We can walk through all elements (call current element item) of initial "set" (I noticed repeating elements in your example, so it is not true set).
Now scan A[] list from the last entry to the beginning. (This trick helps to avoid repeating usage of the same item).
If A[i - item] contains subsets with size < k, we can add all these subsets to A[i] appending item.
After full scan A[x] will contain subsets of size k and less, having sum x, and we can filter only those of size k
Example of output of my quick-made Delphi program for the next data:
Lst := [1,2,3,3,4,5,6,7];
k := 3;
sum := 10;
3 3 4
2 3 5 //distinct 3's
2 3 5
1 4 5
1 3 6
1 3 6 //distinct 3's
1 2 7
To exclude variants with distinct repeated elements (if needed), we can use non-first occurence only for subsets already containing the first occurence of item (so 3 3 4 will be valid while the second 2 3 5 won't be generated)
I literally translate my Delphi code into C++ (weird, I think :)
int main()
{
vector<vector<vector<int>>> A;
vector<int> Lst = { 1, 2, 3, 3, 4, 5, 6, 7 };
int k = 3;
int sum = 10;
A.push_back({ {0} }); //fictive array to make non-empty variant
for (int i = 0; i < sum; i++)
A.push_back({{}});
for (int item : Lst) {
for (int i = sum; i >= item; i--) {
for (int j = 0; j < A[i - item].size(); j++)
if (A[i - item][j].size() < k + 1 &&
A[i - item][j].size() > 0) {
vector<int> t = A[i - item][j];
t.push_back(item);
A[i].push_back(t); //add new variant including current item
}
}
}
//output needed variants
for (int i = 0; i < A[sum].size(); i++)
if (A[sum][i].size() == k + 1) {
for (int j = 1; j < A[sum][i].size(); j++) //excluding fictive 0
cout << A[sum][i][j] << " ";
cout << endl;
}
}
Here is a complete solution in Python. Translation to C++ is left to the reader.
Like the usual subset sum, generation of the doubly linked summary of the solutions is pseudo-polynomial. It is O(count_values * distinct_sums * depths_of_sums). However actually iterating through them can be exponential. But using generators the way I did avoids using a lot of memory to generate that list, even if it can take a long time to run.
from collections import namedtuple
# This is a doubly linked list.
# (value, tail) will be one group of solutions. (next_answer) is another.
SumPath = namedtuple('SumPath', 'value tail next_answer')
def fixed_sum_paths (array, target, count):
# First find counts of values to handle duplications.
value_repeats = {}
for value in array:
if value in value_repeats:
value_repeats[value] += 1
else:
value_repeats[value] = 1
# paths[depth][x] will be all subsets of size depth that sum to x.
paths = [{} for i in range(count+1)]
# First we add the empty set.
paths[0][0] = SumPath(value=None, tail=None, next_answer=None)
# Now we start adding values to it.
for value, repeats in value_repeats.items():
# Reversed depth avoids seeing paths we will find using this value.
for depth in reversed(range(len(paths))):
for result, path in paths[depth].items():
for i in range(1, repeats+1):
if count < i + depth:
# Do not fill in too deep.
break
result += value
if result in paths[depth+i]:
path = SumPath(
value=value,
tail=path,
next_answer=paths[depth+i][result]
)
else:
path = SumPath(
value=value,
tail=path,
next_answer=None
)
paths[depth+i][result] = path
# Subtle bug fix, a path for value, value
# should not lead to value, other_value because
# we already inserted that first.
path = SumPath(
value=value,
tail=path.tail,
next_answer=None
)
return paths[count][target]
def path_iter(paths):
if paths.value is None:
# We are the tail
yield []
else:
while paths is not None:
value = paths.value
for answer in path_iter(paths.tail):
answer.append(value)
yield answer
paths = paths.next_answer
def fixed_sums (array, target, count):
paths = fixed_sum_paths(array, target, count)
return path_iter(paths)
for path in fixed_sums([1,2,3,3,4,5,6,9], 10, 3):
print(path)
Incidentally for your example, here are the solutions:
[1, 3, 6]
[1, 4, 5]
[2, 3, 5]
[3, 3, 4]
You should first sort the so called array. Secondly, you should determine if the problem is actually solvable, to save time... So what you do is you take the last k elements and see if the sum of those is larger or equal to the x value, if it is smaller, you are done it is not possible to do something like that.... If it is actually equal yes you are also done there is no other permutations.... O(n) feels nice doesn't it?? If it is larger, than you got a lot of work to do..... You need to store all the permutations in an seperate array.... Then you go ahead and replace the smallest of the k numbers with the smallest element in the array.... If this is still larger than x then you do it for the second and third and so on until you get something smaller than x. Once you reach a point where you have the sum smaller than x, you can go ahead and start to increase the value of the last position you stopped at until you hit x.... Once you hit x that is your combination.... Then you can go ahead and get the previous element so if you had 1,1,5, 6 in your thingy, you can go ahead and grab the 1 as well, add it to your smallest element, 5 to get 6, next you check, can you write this number 6 as a combination of two values, you stop once you hit the value.... Then you can repeat for the others as well.... You problem can be solved in O(n!) time in the worst case.... I would not suggest that you 10^27 combinations, meaning you have more than 10^27 elements, mhmmm bad idea do you even have that much space??? That's like 3bits for the header and 8 bits for each integer you would need 9.8765*10^25 terabytes just to store that clossal array, more memory than a supercomputer, you should worry about whether your computer can even store this monster rather than if you can solve the problem, that many combinations even if you find a quadratic solution it would crash your computer, and you know what quadratic is a long way off from O(n!)...
A brute force method using recursion might look like this...
For example, given variables set, x, k, the following pseudo code might work:
setSumStructure find(int[] set, int x, int k, int setIdx)
{
int sz = set.length - setIdx;
if (sz < x) return null;
if (sz == x) check sum of set[setIdx] -> set[set.size] == k. if it does, return the set together with the sum, else return null;
for (int i = setIdx; i < set.size - (k - 1); i++)
filter(find (set, x - set[i], k - 1, i + 1));
return filteredSets;
}

C++ : Inserting Operator + and - In an array and check if it is possible to create n

Problem : If you were given a number n, you will have an array with (n-1) index. with the 1st index containing 1, 2nd index containing 2, and n-1 index containing n-1. Given those sets of numbers, How can one check when + or -, the array can be equal to n?
Example :
n = 3, Array = {1,2}
+1 +2 = 3 (True)
n = 4, Array = {1,2,3}
-1 + 2 + 3 = 4 (True)
n = 5, Array = {1,2,3,4}
No possible combination
I tried too long to think about it and still haven't come up with the right answer :(
If you ale looking for simple solvable/not solvable answer, then it seems the answer if very simple
(sum - n) % 2 != 0 // => non-solvable
Here is result of an experiment:
When n gets larger it becomes easier to subtract necessary sum and there are plenty of possible solutions.

Finding the smallest possible number which cannot be represented as sum of 1,2 or other numbers in the sequence

I am a newbie in C++ and need logical help in the following task.
Given a sequence of n positive integers (n < 10^6; each given integer is less than 10^6), write a program to find the smallest positive integer, which cannot be expressed as a sum of 1, 2, or more items of the given sequence (i.e. each item could be taken 0 or 1 times). Examples: input: 2 3 4, output: 1; input: 1 2 6, output: 4
I cannot seem to construct the logic out of it, why the last output is 4 and how to implement it in C++, any help is greatly appreciated.
Here is my code so far:
#include<iostream>
using namespace std;
const int SIZE = 3;
int main()
{
//Lowest integer by default
int IntLowest = 1;
int x = 0;
//Our sequence numbers
int seq;
int sum = 0;
int buffer[SIZE];
//Loop through array inputting sequence numbers
for (int i = 0; i < SIZE; i++)
{
cout << "Input sequence number: ";
cin >> seq;
buffer[i] = seq;
sum += buffer[i];
}
int UpperBound = sum + 1;
int a = buffer[x] + buffer[x + 1];
int b = buffer[x] + buffer[x + 2];
int c = buffer[x + 1] + buffer[x + 2];
int d = buffer[x] + buffer[x + 1] + buffer[x + 2];
for (int y = IntLowest - 1; y < UpperBound; y++)
{
//How should I proceed from here?
}
return 0;
}
What the answer of Voreno suggests is in fact solving 0-1 knapsack problem (http://en.wikipedia.org/wiki/Knapsack_problem#0.2F1_Knapsack_Problem). If you follow the link you can read how it can be done without constructing all subsets of initial set (there are too much of them, 2^n). And it would work if the constraints were a bit smaller, like 10^3.
But with n = 10^6 it still requires too much time and space. But there is no need to solve knapsack problem - we just need to find first number we can't get.
The better solution would be to sort the numbers and then iterate through them once, finding for each prefix of your array a number x, such that with that prefix you can get all numbers in interval [1..x]. The minimal number that we cannot get at this point is x + 1. When you consider the next number a[i] you have two options:
a[i] <= x + 1, then you can get all numbers up to x + a[i],
a[i] > x + 1, then you cannot get x + 1 and you have your answer.
Example:
you are given numbers 1, 4, 12, 2, 3.
You sort them (and get 1, 2, 3, 4, 12), start with x = 0, consider each element and update x the following way:
1 <= x + 1, so x = 0 + 1 = 1.
2 <= x + 1, so x = 1 + 2 = 3.
3 <= x + 1, so x = 3 + 3 = 6.
4 <= x + 1, so x = 6 + 4 = 10.
12 > x + 1, so we have found the answer and it is x + 1 = 11.
(Edit: fixed off-by-one error, added example.)
I think this can be done in O(n) time and O(log2(n)) memory complexities.
Assuming that a BSR (highest set bit index) (floor(log2(x))) implementation in O(1) is used.
Algorithm:
1 create an array of (log2(MAXINT)) buckets, 20 in case of 10^6, Each bucket contains the sum and min values (init: min = 2^(i+1)-1, sum = 0). (lazy init may be used for small n)
2 one pass over the input, storing each value in the buckets[bsr(x)].
for (x : buffer) // iterate input
buckets[bsr(x)].min = min(buckets[bsr(x)].min, x)
buckets[bsr(x)].sum += x
3 Iterate over buckets, maintaining unreachable:
int unreachable = 1 // 0 is always reachable
for(b : buckets)
if (unreachable >= b.min)
unreachable += b.sum
else
break
return unreachable
This works because, assuming we are at bucket i, lets consider the two cases:
unreachable >= b.min is true: because this bucket contains values in the range [2^i...2^(i+1)-1], this implies that 2^i <= b.min. in turn, b.min <= unreachable. therefor unreachable+b.min >= 2^(i+1). this means that all values in the bucket may be added (after adding b.min all the other values are smaller) i.e. unreachable += b.sum.
unreachable >= b.min is false: this means that b.min (the smallest number the the remaining sequence) is greater than unreachable. thus we need to return unreachable.
The output of the second input is 4 because that is the smallest positive number that cannot be expressed as a sum of 1,2 or 6 if you can take each item only 0 or 1 times. I hope this can help you understand more:
You have 3 items in that list: 1,2,6
Starting from the smallest positive integer, you start checking if that integer can be the result of the sum of 1 or more numbers of the given sequence.
1 = 1+0+0
2 = 0+2+0
3 = 1+2+0
4 cannot be expressed as a result of the sum of one of the items in the list (1,2,6). Thus 4 is the smallest positive integer which cannot be expressed as a sum of the items of that given sequence.
The last output is 4 because:
1 = 1
2 = 2
1 + 2 = 3
1 + 6 = 7
2 + 6 = 8
1 + 2 + 6 = 9
Therefore, the lowest integer that cannot be represented by any combination of your inputs (1, 2, 6) is 4.
What the question is asking:
Part 1. Find the largest possible integer that can be represented by your input numbers (ie. the sum of all the numbers you are given), that gives the upper bound
UpperBound = sum(all_your_inputs) + 1
Part 2. Find all the integers you can get, by combining the different integers you are given. Ie if you are given a, b and c as integers, find:
a + b, a + c, b + c, and a + b + c
Part 2) + the list of integers, gives you all the integers you can get using your numbers.
cycle for each integer from 1 to UpperBound
for i = 1 to UpperBound
if i not = a number in the list from point 2)
i = your smallest integer
break
This is a clumsy way of doing it, but I'm sure that with some maths it's possible to find a better way?
EDIT: Improved solution
//sort your input numbers from smallest to largest
input_numbers = sort(input_numbers)
//create a list of integers that have been tried numbers
tried_ints = //empty list
for each input in input_numbers
//build combinations of sums of this input and any of the previous inputs
//add the combinations to tried_ints, if not tried before
for 1 to input
//check whether there is a gap in tried_ints
if there_is_gap
//stop the program, return the smallest integer
//the first gap number is the smallest integer

Loop Explanation in Counting Sort

Could somebody please explain to me the purpose of the second loop in this implementation of counting sort?:
short c[RADIX_MAX] = {0};
int i;
for (i = 0; i < LEN_MAX; i++) {
if (i == len)
break;
int ind = a.getElem(i);
c[ind]++;
}
for (i = 1; i < RADIX_MAX; i++) {
if (i == radix)
break;
c[i] += c[i - 1];
}
for (i = LEN_MAX - 1; i >= 0; i--) {
int j = i - LEN_MAX + len;
if (j < 0)
break;
int ind = a.getElem(j);
short t = ind;
ind = --c[ind];
b.setElem(ind, t);
}
Counting sort works by calculating the target index of each element to be sorted from the value of the element itself. There are three passes involved:
In the first loop, each element is counted: for example our array has six "A"s and two "B"s, five "C"s and so on.
In the second loop, the index where each element goes is calculated. If there are six "A"s, then the first "B" needs to go at index 6 (in 0-based indexing). What the counting sort does is a bit more complicated in order to make the code simpler and the sort stable. In the third loop it will traverse the original array in reverse order, so in the second loop it calculates the index not of the first instance of a given value, but of the last. In our example above, the last "A" needs to appear at index 5, but the last "B" needs to go at index 6 ("A"s) + 2 ("B"s) - 1 (zero based) = index 7. So for each value it calculates the ending index of that value. It walks the count array forward, adding the previosely calculated count to the current count. So in our count array, the value for "A" remains at 6 (no previous element), the value for "B" is 6+2=8 (six "A" + two "B"s), the value for C is now 6+2+5=13 (six "A"s + two "B"s + five "C"s), and so on
In the last loop, the values are inserted in their position, decrementing the indexes as we go along. So the last of the "B"s is inserted at index 7, the one before that at index 6, and so on. This preserves the original order of equal elements, making the sort stable which is essential for Radix sort.
For each digit we count index where it starts from in sorted array.
Example:
array: 0 0 0 0 2 2 3 3 3 9 9
index: 0 1 2 3 4 5 6 7 8 9 10
Then c[0] = 0, c[1] = 4, c[2] = 4, c [3] = 6, c[4] = 9, ... c[9] = 9.
Index in sorted array where digit appears depends on index of previous digit and number of previous digit. Second loop counts this.

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.