In the classic problem of the Towers of Hanoi, you have three towers and N disks of different sizes which can slide onto any tower. The puzzle starts with disks sorted in ascending order of size from top to bottom (i.e., each disk sits on top of an even larger one). You have the following constraints:
- Only one disk can be moved at a time.
- A disk is slid off the top of one tower onto another tower.
- A disk cannot be placed on top of a smaller disk.
Write a program to move the disks from the first tower to the last using stacks.
Example1:
Input: A = [2, 1, 0], B = [], C = [] Output: C = [2, 1, 0]Example2:
Input: A = [1, 0], B = [], C = [] Output: C = [1, 0]Note:
A.length <= 14
I applied the "recursive leap of faith" (references near bottom) and wrote the solution
class Solution {
public:
void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
if (A.size() == 0) {
return;
}
int base = A.front();
A.erase(A.begin());
hanota(A, C, B);
C.push_back(base);
hanota(B, A, C);
}
};
the result is:
AddressSanitizer: DEADLYSIGNAL
=================================================================
==20==
ERROR: AddressSanitizer:
stack-overflow on address 0x7ffc5b035f48
(pc 0x00000031f5ee bp 0x7ffc5b036790 sp 0x7ffc5b035f50 T0)
==20==
ABORTING
I draw a call graph

why the famous "recursive leap of faith" broken here? Is it related to improper stop condition?
For reference:
- Brian Harvey: Computer Science Logo Style
Vol. 1 Symbolic Computing,
Ch. 8: Practical Recursion: the Leap of Faith - K. Schwarz, Z. Galant: Stanford CS106B
slides on Thinking Recursively
The problem is that with
C.push_back(base);you put a disc on stackCthat through recursion may be called stackA, and that is a problem, because that disc should not be moved at this stage, yet that deeper call (that identifies that stack asA) will want to clear stackAcompletely, also moving that (large) disc again! As a side note, this will also lead to invalid moves: where the large disc is moved on top of a smaller disc.This disc that is put on stack
Cis polluting the idea of "recursive leap of faith", as now your recursive process should somehow know which discs should not be moved.Details
Let's just follow the process with input
A={2,1,0}, B={}, C={}, until we get at a wrong manipulation:The top-level call will remove 2 from
Aand make a recursive call to move{1,0}from stackAtoB. When we get back from that recursive call, all is fine, and we have this state:Then 2 is appended to
C:Note that this disc 2 is at its final spot and it should never have to move again.
We now make a second recursive call aiming to move
{1,0}on top ofC. Let's look at this second level in the recursion tree:Second level:
At this point the local variables
A,BandCare associated with the stacks as follows (which is still fine):The value 1 is removed from stack
A, and we get:A first recursive call is made with the aim to move that zero to stack
B. We will look at that third-level call in detail:Third level:
Again new local variables get to reference the stacks as follows:
The 0 is removed from
A, a recursive call is made that has nothing to do (fine), and 0 is appended toC, so we arrive in this state:Now we get at the second recursive call made at this third level, which brings us at a fourth-level call.
Fourth level:
Here the stacks are labeled as follows:
...but that is problematic, since this would mean we want to move the 2 from stack
Aon top ofC. This is not desired, nor is it valid.Remember we said that disc 2 should never have to move again, yet that is what is going to happen here. Stack
Ashould not be emptied. This disc 2 is polluting the algorithm.We could go on and analyse the next steps that are taken, and we would not only see invalid stacks, but also see repetitions of the same states. I don't think it is worth to continue this analysis however, because we already know it went wrong at this point.
Solutions
There are several solutions to this.
You could for instance mark the discs that should not be moved in deeper recursion, but that is not a very elegant solution.
Since you are OK with doing an action like
A.erase(A.begin());which represents a non-Hanoi move (for the purpose of recursion), I suppose you could not object to doing the inverseC.insert(C.begin(), base)for the same purpose.So change:
to:
This is of course not representing the actual move when it happens, but that is also true for
A.erase(A.begin());.Alternative
Usually you would have to do this with performing only valid operations on the stacks, i.e. moving the disc at the top of a stack, and not artificially removing a disc at the bottom.
To make that work, it will be good to add a parameter that indicates how many discs need to be moved from
AtoC, so avoiding moving discs that are supposed to stay where they are (at that stage).So:
The initial call needs to provide the total number of discs for
n: