汉诺塔问题

方法一:递归

此题为典型的递归问题。有3根柱子A、B、C我们想将A上的所有盘子挪到C上面,那么就必须先将上面的n-1个盘子先挪到B上面,再将A上最底下那个盘子挪到C上,最后再将B上的n-1个盘子挪到C上。

class Solution {

    public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
        this.doHanota(A, B, C, A.size());
    }
    /**
     *count为需要挪动的盘子个数
     *函数作用是将A上面的count个盘子挪到C上面
     *
     */
    private void doHanota(List<Integer> A, List<Integer> B, List<Integer> C, int count){
        //A为空或者count为0,直接返回
        if(A == null || A.size() == 0 || count == 0){
            return;
        }
        //如果只需要移动一个盘子,直接操作
        if(count == 1){
            Integer x = A.remove(A.size() - 1);
            C.add(x);
            return;
        }
        //将A上面的count-1个盘子移动到B上面
        this.doHanota(A, C, B, count - 1);
        //移动A上面的第count个盘子到C上
        this.doHanota(A, B, C, 1);
        //移动B上面的count-1个盘子移动到C上面
        this.doHanota(B, A, C, count - 1);
    }
}

方法二:用栈模拟递归

class Solution {
    static class Frame {
        List<Integer> A; String a;
        List<Integer> B; String b;
        List<Integer> C; String c;
        int n;
        int stage; // 0: 准备做第1步, 1: 准备做第2步(移动), 2: 准备做第3步, 3: 结束

        Frame(List<Integer> A, String a, List<Integer> B, String b, List<Integer> C, String c, int n) {
            this.A = A; this.a = a;
            this.B = B; this.b = b;
            this.C = C; this.c = c;
            this.n = n;
            this.stage = 0;
        }
    }

    public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
        Deque<Frame> stack = new ArrayDeque<>();
        stack.push(new Frame(A, "A", B, "B", C, "C", A.size()));

        while (!stack.isEmpty()) {
            Frame f = stack.peek();

            if (f.n == 0) {                 // 等价于递归里的 count==0
                stack.pop();
                continue;
            }

            if (f.n == 1) {                 // 等价于递归里的 count==1
                moveOne(f.A, f.a, f.C, f.c);
                stack.pop();
                continue;
            }

            if (f.stage == 0) {
                // 第1步:H(A, C, B, n-1)
                f.stage = 1;
                stack.push(new Frame(f.A, f.a, f.C, f.c, f.B, f.b, f.n - 1));
            } else if (f.stage == 1) {
                // 第2步:move A -> C
                f.stage = 2;
                moveOne(f.A, f.a, f.C, f.c);
            } else if (f.stage == 2) {
                // 第3步:H(B, A, C, n-1)
                f.stage = 3;
                stack.push(new Frame(f.B, f.b, f.A, f.a, f.C, f.c, f.n - 1));
            } else {
                // stage == 3:这一帧三步都做完了
                stack.pop();
            }
        }
    }

    private void moveOne(List<Integer> from, String fromName, List<Integer> to, String toName) {
        // 题目保证合法移动;这里直接弹出栈顶即可
        int x = from.remove(from.size() - 1);
        to.add(x);
        System.out.println(fromName + "->" + toName);
    }
}

方法三:直接循环迭代

如果盘子数 n 是 奇数:按对 (A,C) -> (A,B) -> (B,C) 循环

如果 n 是 偶数:按对 (A,B) -> (A,C) -> (B,C) 循环

class Solution {
    public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
        int n = A.size();
        if (n == 0) return;

        // n为奇数:AC, AB, BC
        // n为偶数:AB, AC, BC
        while (C.size() != n) {
            if ((n & 1) == 1) {
                moveBetween(A, "A", C, "C");
                if (C.size() == n) break;

                moveBetween(A, "A", B, "B");
                if (C.size() == n) break;

                moveBetween(B, "B", C, "C");
            } else {
                moveBetween(A, "A", B, "B");
                if (C.size() == n) break;

                moveBetween(A, "A", C, "C");
                if (C.size() == n) break;

                moveBetween(B, "B", C, "C");
            }
        }
    }

    // 在两根柱子之间做一次“唯一合法移动”
    private void moveBetween(List<Integer> X, String xName, List<Integer> Y, String yName) {
        if (X.isEmpty() && Y.isEmpty()) return;

        if (X.isEmpty()) {
            moveOne(Y, yName, X, xName);
            return;
        }
        if (Y.isEmpty()) {
            moveOne(X, xName, Y, yName);
            return;
        }

        int topX = X.get(X.size() - 1);
        int topY = Y.get(Y.size() - 1);

        if (topX < topY) {
            moveOne(X, xName, Y, yName);
        } else {
            moveOne(Y, yName, X, xName);
        }
    }

    private void moveOne(List<Integer> from, String fromName, List<Integer> to, String toName) {
        int disk = from.remove(from.size() - 1);
        to.add(disk);

        // 如果你不需要打印,删掉这一行即可
        // System.out.println(fromName + "->" + toName);
    }
}

移动次数解释说明

为什么移动次数是2^n - 1

从递归代码可以看出,移动次数为T(n)

T(n)=T(n−1)+1+T(n−1)=2T(n−1)+1

展开递推:

T(n)​=2T(n−1)+1=2(2T(n−2)+1)+1=4T(n−2)+2+1=8T(n−3)+4+2+1…=2n−1T(1)+(2^(n−2)+2^(n−3)+⋯+2+1)​

代入 T(1)=1T(1)=1:

T(n)=2^(n−1)+(2^(n−2)+⋯+2+1)

括号里是等比数列的前2n-2项,公比是2,代入等比数列求和公式 Sn =  a1(1-q^n)/(1-q)得

2^(n−2)+⋯+2+1=2^(n−1)−1

T(n)=2^(n−1)+(2^(n−1)−1)=2^n −1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值