Related
I've been working on learning backtracking and I know how the general template goes, but I'm struggling to fully understand how the algorithm backtracks, specifically how it knows when to pop from a current solution and how often to.
I know it should be that we have a base case, and when we hit this base case, we then return from this current iteration. But then I'm not fully sure on why we pop from a solution many times until we start exploring again.
For example, I've been working on the classic "Generate Parentheses" problem:
Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
E.g.
Input: n = 3
Output: ["((()))","(()())","(())()","()(())","()()()"]
Here's my working solution after applying my existing knowledge of how the template should be, but I just can't work out how currCombo.pop() falls into the thinking and visualising how it works.
function generateParenthesis(n) {
const result = [];
backtrack(result, n, 0, 0, []);
return result;
};
function backtrack(result, n, open, close, currCombo) {
if (currCombo.length === 2 * n) {
result.push(currCombo.join(''));
return;
}
if (open < n) {
currCombo.push('(');
backtrack(result, n, open + 1, close, currCombo);
currCombo.pop();
}
if (close < open) {
currCombo.push(')');
backtrack(result, n, open, close + 1, currCombo);
currCombo.pop();
}
}
So for example, the algorithm first outputs:
"((()))"
And the second result is then:
"(()())"
But how does the algorithm know it needs to pop off 3 close brackets and then 1 open bracket, and then to continue adding brackets from there? I debugged the code line by line and just couldn't see why it would do a certain number of pop operations and then continue.
I've tried checking out Youtube videos, articles, blogs, but I just can't visualise what the algorithm is doing and how it's making the decisions that it is when it is.
Any help much appreciated. Thanks
There's nothing for the algorithm to "know" exactly; each pop is an undo of the push that set up the call frame for the recursion. That's it. Most of the logic has to do with your if statements that protect the recursion, ensuring balance.
Think of it as a depth-first graph exploration where each node is a possible arrangement of up to n ( parentheses and n ) parentheses. The open and close variables don't encode any unique information. These variables are conveniences to avoid each frame having to count the already-chosen parentheses, and having these counts enables you to avoid exporing pointless subtrees which can never produce a result, like (((( on n=3.
Each recursive call frame begins by asking whether it's at a result point. If so, save the result and return. If not, if it'd be possible to reach a result by adding a ( to the end of the string so far, explore that subtree, then undo the move by popping, resetting to a clean state. Next, try adding ) if that might lead to a result, explore the subtree, and undo the move. After undoing any modifications made and exploring one or both subtrees, return to the caller since all possibilities have been explored rooted at this node in the graph.
Let's trace how we get to the first result "((()))" and then to "(()())".
On the first call, open < n ("the first branch") is true, so we push ( and spawn one recursive call with open = 1. This initial ( stays in the result for the duration of the algorithm; close < open ("the second branch") is false for the first call frame.
On the second call, both branches are true, so we'll eventually make two recursive calls, but for starters we try pushing ( again to give the string (( and recursing with open = 2.
On the third call, both branches are true, so we'll eventually make two recursive calls, but for starters we try pushing ( again to give the string ((( and recursing with open = 3.
On the fourth call, the first branch is false, so we only perform one recursion from the second branch close < open with the string ((() and open = 3, close = 1.
On the fifth call, the first branch is false, so we only perform one recursion from the second branch close < open with the string ((()) and open = 3, close = 2.
On the sixth call, the first branch is false, so we only perform one recursion from the second branch close < open with the string ((())) and open = 3, close = 3.
On the seventh call, currCombo.length === 2 * n is true so we add ((())) to the result and return back up one call frame.
Now the sixth call resumes executing and there's no code left to run; recall that for this frame, the first branch was false, so we skipped it, and we've already explored the second branch recursively. Pop the string to ((()) and return to the caller.
Now the fifth call resumes executing and there's no code left to run; recall that for this frame, the first branch was false, so we skipped it, and we've already explored the second branch recursively. Pop the string to ((() and return to the caller.
Now the fourth call resumes executing and there's no code left to run; recall that for this frame, the first branch was false, so we skipped it, and we've already explored the second branch recursively. Pop the string to ((( and return to the caller.
Now the third call resumes executing but we haven't explored the second branch yet, so pop ( again to give the string ((, then push ) and recurse on (() and open = 2, close = 1.
A new call, the eighth call, begins, and both branches are true, so we'll eventually make two recursive calls, but for starters we try pushing ( again to give the string (()( and recursing with open = 3, close = 1.
A new call, the ninth call, begins. The first branch is false, so we only perform one recursion from the second branch close < open with the string (()() and open = 3, close = 2.
A new call, the tenth call, begins. The first branch is false, so we only perform one recursion from the second branch close < open with the string (()()) and open = 3, close = 3.
A new call, the eleventh call, begins.
On this call, currCombo.length === 2 * n is true so we add (()()) to the result and return back up one call frame.
Now the tenth call resumes executing and there's no code left to run; recall that for this frame, the first branch was false, so we skipped it, and we've already explored the second branch recursively. Pop the string to (()() and return to the caller.
Now the ninth call resumes executing and there's no code left to run; recall that for this frame, the first branch was false, so we skipped it, and we've already explored the second branch recursively. Pop the string to (()( and return to the caller.
Now the eighth call resumes executing but we haven't explored the second branch yet, so pop ( again to give the string ((), then push ) and recurse on (()) and open = 2, close = 2.
... I'll stop here; finish tracing the execution to the next result (())() which you can see we're well on our way to building.
But how does the algorithm know it needs to pop off 3 close brackets and then 1 open bracket, and then to continue adding brackets from there?
It popped off 3 close parens because there was nothing left to explore on those call frames. The first branch was false and the second branch had already been explored.
The reason the 1 open paren was popped next is because the recursive subtree of possibilities starting with the open paren had already been explored fully, but the subtree rooted with ) hadn't been explored yet. When we left off our algorithm trace, we were just launching into exploring that subtree. When the subtree is exhausted and any results that it ultimately yielded had been stored, that paren would also pop off and the frame would be completely explored.
At that point, its caller (the second call in the above example) would still need to explore the subtree starting with (), open = 1, close = 1, since it had only tried ((, open = 2, close = 0 up to that point. In other words, it'll have explored the ( branch but not the ) branch that will ultimately lead to the last result ()()().
Here's a visualization of the recursive call tree:
function generateParenthesis(n) {
const result = [];
backtrack(result, n, 0, 0, []);
return result;
};
function backtrack(result, n, open, close, currCombo, depth=0) {
console.log(`${" ".repeat(depth * 2)}enter '${currCombo.join("")}'`);
if (currCombo.length === 2 * n) {
result.push(currCombo.join(''));
console.log(`${" ".repeat(depth * 2)}result '${currCombo.join("")}'`);
console.log(`${" ".repeat(depth * 2)}exit '${currCombo.join("")}'`);
return;
}
if (open < n) {
currCombo.push('(');
backtrack(result, n, open + 1, close, currCombo, depth + 1);
currCombo.pop();
}
if (close < open) {
currCombo.push(')');
backtrack(result, n, open, close + 1, currCombo, depth + 1);
currCombo.pop();
}
console.log(`${" ".repeat(depth * 2)}exit '${currCombo.join("")}'`);
}
generateParenthesis(3);
If that's too complex, you can dial it back to n = 2, trace that, then do n = 3.
Below is an example of quicksort. I was wondering how two recursive method call inside quicksort method works i.e in what sequence it'll execute? So to check in what sequence I placed syso after each method (check output).My doubt why this sequence?Thus it depends on any conditions? if so, what condition? It would be helpful if explained the logic in detail.
Thank you in advance :)
void quicksort(int a[], int p, int r)
{
if(p < r)
{
int q;
q = partition(a, p, r);
System.out.println("q:"+q);
quicksort(a, p, q);
System.out.println("1");
quicksort(a, q+1, r);
System.out.println("2");
}
}
int partition(int a[], int p, int r)
{
System.out.println("p:"+p+" r:"+r);
int i, j, pivot, temp;
pivot = a[p];
i = p;
j = r;
while(1)
{
while(a[i] < pivot && a[i] != pivot)
i++;
while(a[j] > pivot && a[j] != pivot)
j--;
if(i < j)
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
else
{
return j;
}
}
}
Output
p:0 r:7
q:4
p:0 r:4
q:0
1
p:1 r:4
q:1
1
p:2 r:4
q:3
p:2 r:3
q:2
1
2
1
2
2
2
1
p:5 r:7
q:7
p:5 r:7
q:6
p:5 r:6
q:5
1
2
1
2
1
2
2
Would like to know why the gap between method calls?i.e how println(placed after method calls) statement getting executed w/o executing method call?
Yes, it depends on conditions: specifically, the values of p and r on each call. Each instance of the sort will do the two calls in order: none of the execution branches will get to the second call until the first call of that branch is completely done.
You will get a much nicer trace if you put a println at the top of the function that displays the parameter values. You might want to place one after you compute the value of q, as well. Try that, and see whether it tells you the rest of the story.
Okay, you've done the printing ... and you don't see the reason for that gap? When you get to the output line "q:2", you've made five calls to quicksort, and the only progress through that sequence is that you've made it past the "1" print for two of them (you're in the second call). Your current stack looks like this, in terms of p and r:
2, 3
2, 4
0, 4
0, 7
This is the right half of the left half (second quarter) of the array, four levels deep. You now have to finish off those calls, which will get you to the "1" print for the "right-half" ones, the "2" print for each of them.
Looking at it another way, you work to partition the array down to single elements. While you're doing this, you stack up calls for smaller partitions. Any call with at least two elements has to finish off both of its partitions before it returns to the next print.
Once you get to a single-element array, you return right away, and get to print the next "1" or "2". If the other partition is also fully atomized, then you get to the next "1" or "2" without any more partitioning calls.
Halfway through, you get to the point where you've fully atomized the left half of the array; that's when you clear out all the outstanding processing, back up the stack, and do all of that printing. Then you recur down the right half, and get a similar effect.
I think you might have an easier time understanding if you'd give yourself a full trace. Either follow this in your debugger, or modify and add print statements so that (1) you have a unique, easily-read output for every line, rather than 8 lines each of "1" and "2" that you can't tell apart; (2) you can also trace the return from each routine. The objective here is to be able to recognize where you are in the process at each output line.
Yes, it's another programming problem to be able to print out things such as
1.L.R.R
1.1.2
(0,4) L
...
... or whatever format you find readable.
I don't quite understand this piece of code. So if for example n = 5 and we have:
array[5] = {13, 27, 78, 42, 69}
Would someone explain please?
All I understand is if n = 1, that is the lowest.
But when n = 5, we would get the 4th index and compare it to the 4th index and check which is the smallest and return the smallest, then take the 4th index and compare it to the 3rd index and check which one is the smallest and return the smallest? I am confused.
int min(int a, int b)
{
return (a < b) ? a: b;
}
// Recursively find the minimum element in an array, n is the length of the
// array, which you assume is at least 1.
int find_min(int *array, int n)
{
if(n == 1)
return array[0];
return min(array[n - 1], find_min(array, n - 1));
}
Given your array:
1. initial call: find_min(array, 5)
n!=1, therefore if() doesn't trigger
2. return(min(array[4], find_min(array, 4)))
n!=1, therefore if doesn't trigger
3. return(min(array[3], find_min(array,3)))
n!=1, therefore if doesn't trigger
4. return(min(array[2], find_min(array,2)))
n!=1, threfore if() doesn't trigger
5. return(min(array[1], find_min(array,1)))
n==1, so return array[0]
4. return(min(array[1], array[0]))
return(min(13, 27)
return(13)
3. return(min(array[2], 13))
etc...
It's quite simple. Run through the code using the example you gave.
On the first run through find_min(), it will return the minimum of the last element in the array (69) and the minimum of the rest of the array. To calculate the minimum of the rest of the array, it calls itself, i.e. it is recursive. This 2nd-level call will compare the number 42 (the new "last" element) with the minimum from the rest of the array, and so on. The final call to find_min() will have n=1 with the array "{13}", so it will return 13. The layer that called it will compare 13 with 27 and find that 13 is less so it will return it, and so on back up the chain.
Note: I assume the backward quotes in your code are not supposed to be there.
The solution uses recursion to compute the minimum for the smallest possible comparison set and comparing that result with the next bigger set of numbers. Each recursive call returns a result that is compared against the next element in a backward manner until the minimum value bubbles up to the top. Recursion appears to be tricky at first, but can be quite effective once you get familiar with it.
I'm working to understand recursion more in depth and I'm struggling with WHY it works the way it does. I know that this function returns the square of the previous return (2, 4, 16, 256, etc.), but I'm wonder how it gets the answer.
My understanding of recursion is that it iterates back down to the base case, but that leads me to believe that it would eventually always return the base case. How does it work its way back up to returning something new every time?
int p1a(int num) {
if (num == 1) { return 2; }
else {
return pow(p1a(num-1), 2);
}
}
Here's an example of my thinking
num = 3
passes through the base case and hits pow(p1a(num-1), 2)
moves back to the start
again passes through the base case and hits pow(p1a(num-1), 2)
at this point, num = 1, so it would return 2
How is it working its way back up to return 16? I understand what the function returns, but I'm stuck on the process of getting there.
You're thinking about the steps linearly, while the execution is actually nested (represented by indenting):
call p1a(3)
call p1a(2)
call p1a(1)
return 2
return pow(2, 2)
return pow(4, 2)
So the final return returns the value 16.
I got this question in an interview. So, this seems to me a messed up Fibonacci seq. sum generator and this gives a stackoverflow. Because if(n==0) should be if(n<3) (exit condition is wrong). What should be the precise answer to this question? What was expected as an answer?
foo (int n) {
if (n == 0) return 1;
return foo(n-1) + foo(n-2);
}
UPDATE
What does this recursive function do?
What do you infer when you see these 3 lines of code. No debugging.
Yes, it's just a recursive Fibonacci method with an incorrect base case (although I don't think I'd use n < 3 either... it depends on how you define it).
As for "what was expected as an answer" - that would depend on the question. Were you asked exactly the question in the title of your post? If so, the answer is "it recurses forever until it blows up the stack when you pass it any value other than 0" - with an explanation, of course. (It will never end because either n-1 isn't 0, or n-2 isn't 0, or both.)
EDIT: The above answers the first question. To answer "What do you infer when you see these 3 lines of code" - I would infer that the developer in question has never run the code with a value other than 0, or doesn't care about whether or not the code works.
The problem with the code posted is that if we evaluate foo(1) we need to find foo(0) and foo (-1), foo(-1) then needs to find foo(-2) and foo(-3) and so on. This will keep putting more calls to foo() until there is no more space in the memory resulting in a stack overflow. How many calls to foo are made will depend on the size of the call stack, which will be implementation specific.
When I see these lines of code I immediately get the impression that whoever wrote it hasn't thought about all the possible inputs that could be fed to the function.
To make a recursive Fibonacci function that doesn't fail for foo(1) or a negative input we get:
foo (int n) {
if( n < 0 ) return 0;
if (n == 0) return 1;
return foo(n-1) + foo(n-2);
}
Edit: perhaps the return for a negative number should be something else, as the fibonacci sequence isn't implicitly defined for negative indexes.
However if we use the extension that fib(-n) = (-1)^(n+1) fib(n) we could get the following code:
int fib(int n) {
if( n == -1){
return 1;
}else if ( n < 0 ){
return ( (-1)^(-n+1 ) * fib(-n) );
}else if (n == 0){
return 1;
}else{
return fib(n-1) + fib(n-2);
}
}
suppose, you call foo ( 1 ), it will have two sub calls of foo(0) and foo(-1). As you can see, once you get to foo(-1), n will decrease indefinitely and will never reach a terminating condition.
The precise answer would be:
foo (int n) {
if (n <= 1) return 1; // assuming 0th and 1st fib is 1
return foo(n-1) + foo(n-2);
}
Its a recursive fibonacci function with 0th element being 1.
Ignoring the incorrect termination condition, the code is the "naive" recursive implementation of the Fibonacci function. It has very poor big-O time complexity. It would work fine for small n, but if n is greater than say 50 (I'm just guessing that number), then it would take "forever" to run.