题目:
汉诺塔的问题比较经典,这里修改一下游戏规则:现在限制不能从最左侧的塔直接移动到最右侧,也不能从最右侧直接移动到最左侧,而是必须经过中间。求当塔有N层的时候,打印最优秀移动过程和最优移动总步数。
例如:当塔为两层的时候,最上层塔记作1,最下层的塔记作2,则打印:
Move 1 from left to mid
Move 1 from mid to right
Move 2 from left to mid
Move 1 from right to mid
Move 1 from mid to left
Move 2 from mid to right
Move 1 from left to mid
Move 1 from mid to right
It will move 8 steps.
我们来使用栈来解决这个问题:
修改后的汉诺塔问题不能让任何塔从“左”直接移动到“右”,也不能直接从“右”移动到“左”,而是要经过中间,也就是说,实际的动作只有4个:“左”到“中”、“中”到“左”、“中”到“右”、“右”到“中”。
现在我们把左、中、右三个地点抽象成栈,依次记为LS、MS、RS。最初所有的塔都压在LS上。那么如上四个动作可以看作是:某一个栈(from)把栈顶元素弹出,然后压入另一个栈中(to),作为这一个栈(to)的栈顶。
例如,如果是7层塔 ,在最初,所有的塔都在LS上,LS从栈顶到栈底依次是1~7,如果现在发生了“左”到“右”的动作,这个动作对应的操作是LS栈将栈顶元素1弹出,然后压入MS栈中,其他元素同理。
一个动作发生的先决条件就是不违反小大的原则。
from弹出的元素num如果想压进to栈中,那么num指必须小于当前栈的栈顶。
还有一个原则不是很明显,但也是非常的重要,叫做相邻不可逆原则:
1.我们很明显将四个动作依次定义为:L->M、M->L、M->R和R->M。
2.L->M和M->L过程互为逆过程,R->M和M->R互为逆过程。
在修改后的汉诺塔游戏中如果想走出最小的步数,那么任何两个相邻的动作都不是互为逆过程。
核心结论:
1.游戏的第一步动作动作一定是L->M。
2.在走出最小步的任何时刻,四个动作中只有一个动作不违反小压大和相邻不可逆原则,另外三个动作一定会违反。
对于结论2,现在进行简单的证明:
因为游戏的第一个动作是L->M,,则以后每一步都会有前一步。
假设前一步是L->M:
1.根据小压大的原则,L->M的动作不会重复发生。
2.根据相邻不可逆原则M->L 的动作不该发生。
3.根据小压大原则M->R和R->M只有一个会达标。
假设前一步的动作是M-L:
1.根据小压大原则,M->L的动作不会重复发生。
2.根据相邻不可逆原则L-M的动作也不会发生。
3.根据小压大原则,M->R和R->M只有一个动作会达标。
假设前一步的动作是M-R:
1.根据小压大原则,M->R的动作不会重复发生。
2.根据相邻不可逆原则R-M的动作也不会发生。
3.根据小压大原则,L->M和M->L只有一个动作会达标。
假设前一步的动作是R-M:
1.根据小压大原则,R->M的动作不会重复发生。
2.根据相邻不可逆原则M-R的动作也不会发生。
3.根据小压大原则,L->M和M->L只有一个动作会达标。
代码:
import java.util.Stack;
public class HanNuoTower {
public enum Action{
No,LToM,MToL,MToR,RToM
}
public int hannuoProblem(int num, String left, String mid, String right){
Stack<Integer> lS = new Stack<Integer>();
Stack<Integer> mS = new Stack<Integer>();
Stack<Integer> rS = new Stack<Integer>();
lS.push(Integer.MAX_VALUE);
mS.push(Integer.MAX_VALUE);
rS.push(Integer.MAX_VALUE);
for(int i = num; i > 0; i--){
lS.push(i);
}
Action[] record = {Action.No};
int step = 0;
while(rS.size() != num + 1){
step += fStackToStack(record, Action.MToL, Action.LToM, lS, mS, left, mid);
step += fStackToStack(record, Action.LToM, Action.MToL, mS, lS, mid, left);
step += fStackToStack(record, Action.RToM, Action.MToR, mS, rS, mid, right);
step += fStackToStack(record, Action.MToR, Action.RToM, rS, mS, right, mid);
}
return step;
}
public static int fStackToStack(Action[] record, Action preNoAct,
Action nowAct, Stack<Integer> fStack, Stack<Integer> tStack,
String from, String to){
if(record[0] != preNoAct && fStack.peek() < tStack.peek()){
tStack.push(fStack.pop());
System.out.print("Move" + tStack.peek() + "from" + from + "to" + to);
record[0] = nowAct;
return 1;
}
return 0;
}
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
参考资料:《程序员代码面试指南》左程云 著