【多路递归】汉诺塔&杨辉三角

一、汉诺塔

1.描述

在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。

请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。
你需要原地修改栈。
示例1:

 输入:A = [2, 1, 0], B = [], C = []
 输出:C = [2, 1, 0]

示例2:

 输入:A = [1, 0], B = [], C = []
 输出:C = [1, 0]

提示:A中盘子的数目不大于14个。

4片圆盘的移动方法:
Tower_of_Hanoi_4.gif

2.解题过程:

找规律:
一片圆盘

  • A->C

两片圆盘

  • A->B
  • A->C
  • B->C

三片圆盘

  • A->C
  • A->B
  • C->B 这一步结束之后,前两个圆盘已经从A移动到了B:A->B
  • A->C 第三个从A移动到了C
  • B->A
  • B->C
  • A->C 这一步结束之后,前两个从B移动到了C:B->C

即三片圆盘的移动方法可以简化成2片的移动方法:把前两片圆盘看做是一片圆盘:

  • A->B
  • A->C
  • B->C
    此移动方法就是2片的移动方法。

总结:两片圆盘的移动方法就是最终的移动方法,大于2片的都能拆解成2片的移动方法。

3.代码
class Solution {
    public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
        remove(A.size(),A,B,C);
    }
    public void remove(int count,List<Integer> A, List<Integer> B, List<Integer> C){
        if (count == 0) {
            return;
        }
        remove(count - 1, A, C, B);
        C.add(A.remove(A.size() - 1));
        remove(count - 1, B, A, C);
    }
}

递归过程:
image.png

递归调用 remove(3, A, B, C):
	count 不为 0,所以执行下面的步骤。
	调用 remove(2, A, C, B):
		递归调用 remove(2, A, C, B):
			count 不为 0,执行下面的步骤。
			再次调用 remove(1, A, B, C):
				递归调用 remove(1, A, B, C):
					count 不为 0,执行下面的步骤。
					调用 remove(0, A, C, B):
						递归调用 remove(0, A, C, B)****:
							count 为 0,直接返回。
					执行 C.add(A.remove(A.size() - 1)),即将盘子 1 从 A 移动到 C:
						A = [2, 3]
						B = []
						C = [1]
					再次调用 remove(0, B, A, C):
						count 为 0,直接返回。
			执行 C.add(A.remove(A.size() - 1)),即将盘子 2 从 A 移动到 B:
				A = [3]
				B = [2]
				C = [1]
			再次调用 remove(1, B, A, C):
				递归调用 remove(1, B, A, C):
					count 不为 0,执行下面的步骤。
					调用 remove(0, B, C, A):
						递归调用 remove(0, B, C, A)****:
							count 为 0,直接返回。
					执行 C.add(B.remove(B.size() - 1)),即将盘子 2 从 B 移动到 C:
						A = [3]
						B = []
						C = [1, 2]
					再次调用 remove(0, A, B, C):
						count 为 0,直接返回。
			执行 C.add(A.remove(A.size() - 1)),即将盘子 3 从 A 移动到 C:
				A = []
				B = []
				C = [1, 2, 3]
	执行 C.add(A.remove(A.size() - 1)),即将盘子 1 从 A 移动到 C:
		A = []
		B = []
		C = [1, 2, 3]

二、杨辉三角

1.题目

给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
PascalTriangleAnimated2.gif
示例 1:

输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]

示例 2:

输入: numRows = 1
输出: [[1]]
2.题解

把杨辉三角左边对齐来看:

1
1  1
1  2  1  
1  3  3  1
1  4  6  4  1

从图中可以看出,已知的元素是每一行的第一个元素和最后一个元素都是1;
其余元素的结果是上一行的前两个的和:
行为i,列为j,那么任一一个元素 [i][j] = [i-1,j-1]+[i-1,j]
其中,当j==0||i==j时,[i][j] = 1
所以,所有元素最终都可以简化为1相加的和。

3.递归解法:
    /**
     * 求第i行第j列元素
     * @param i
     * @param j
     * @return
     */
    public static int element(int i,int j) {
        //每一行的第一个(第0列)和最后一个都是1
        if (j == 0 || i == j) {
            return 1;
        }
        return element(i - 1, j - 1) + element(i - 1, j);
    }

    /**
     * 递归
     * @param numRows
     * @return
     */
    public static List<List<Integer>> generateRecursion(int numRows) {
        List<List<Integer>> lists = new ArrayList<>();
        for (int i = 0; i < numRows; i++) {
            List<Integer> list = new ArrayList<>();
            for (int j = 0; j <= i; j++) {
                //第i行第j列元素
                int element = element(i, j);
                list.add(element);
            }
            lists.add(list);
        }
        return lists;
    }

以上解法超出时间限制

4.递归优化:使用记忆法缓存

每次求出的元素缓存到数组中,判断当前要求的数组里是不是有了,如果有了,就直接从数组中拿出来,不用再递归计算了。

    /**
     * 递归优化
     * @param numRows
     * @return
     */
    public static List<List<Integer>> generateRecursionCache(int numRows) {
        List<List<Integer>> lists = new ArrayList<>();
        //行初始化为numRows
        int[][] arr = new int[numRows][];

        for (int i = 0; i < numRows; i++) {
            //i行的列初始化为i+1
            arr[i] = new int[i + 1];

            List<Integer> list = new ArrayList<>();
            for (int j = 0; j <= i; j++) {
                //第i行第j列元素
                int element = elementCache(i, j, arr);
                list.add(element);
            }
            lists.add(list);
        }
        return lists;
    }

    /**
     * 求第i行第j列元素 使用记忆法优化
     * @param i
     * @param j
     * @return
     */
    public static int elementCache(int i,int j,int[][] arr) {
        if (arr[i][j] != 0) {
            return arr[i][j];
        }
        //每一行的第一个(第0列)和最后一个都是1
        if (j == 0 || i == j) {
            arr[i][j] = 1;
            return 1;
        }
        arr[i][j] = elementCache(i - 1, j - 1, arr) + elementCache(i - 1, j, arr);
        return arr[i][j];
    }
5.使用动态规划
    /**
     * 递归优化 使用动态规划
     * @param numRows
     * @return
     */
    public static List<List<Integer>> generateDynamic(int numRows) {
        List<List<Integer>> lists = new ArrayList<>();
        //行初始化为numRows
        //List<Integer> rowList = new ArrayList<>();
        int[] rowArr = new int[numRows];
        for (int i = 0; i < numRows; i++) {
            List<Integer> list = new ArrayList<>();
            //求第i行元素
            dynamicElement(rowArr, i);
            //这里有瑕疵 又要把数组转成List
            for (int k : rowArr) {
                if (k != 0) {
                    list.add(k);
                }
            }
            lists.add(list);
        }
        return lists;
    }

    /**
     * 计算rowNum行元素 动态规划
     * @param arr 上一行的元素
     * @param rowNum
     * @return
     */
    public static void dynamicElement(int[] arr,int rowNum) {
        if (rowNum == 0) {
            arr[0] = 1;
            return;
        }
        /**
         * 1             rowNum=0
         * 1  1          rowNum=1
         * 1  2  1       rowNum=2
         * 1  3  3  1    rowNum=3     arr=4
         * 1  4  6  4  1 rowNum=4
         */
        //求出第n行的所有元素 原本arr[0]都是1 所以不用管
        for (int i = rowNum; i > 0; i--) {
            arr[i] = arr[i] + arr[i - 1];
        }
    }

动态规划是一种解决问题的算法思想,通常用于解决具有重叠子问题和最优子结构性质的问题。
杨辉三角的过程可以描述为动态规划的一个应用:

  • 定义状态:
    int[] rowArr 数组用于存储当前行的元素,它们构成了杨辉三角的一行。
    lists 是一个 List<List>,用于存储所有行的元素。
  • 状态转移:
    dynamicElement 方法实现了状态转移的逻辑。它从第一行开始计算,依次更新每一行的元素值,直到第 numRows 行。
    在动态规划的思想中,每一行的元素可以通过上一行的元素计算得到。具体来说,每个元素的值等于它上方两个元素之和(例如第 i 行的第 j 列元素等于第 i-1 行的第 j-1 列和第 j 列元素之和)。
  • 填充列表:
    在 generate 方法中,通过调用 dynamicElement 方法来更新 rowArr 数组,然后将非零元素添加到 list 中,最后将 list 添加到 lists 中,完成了对杨辉三角每一行的构建。
6.迭代解法
    public static List<List<Integer>> generate(int numRows) {
        List<List<Integer>> lists = new ArrayList<>();

        List<Integer> listFirst = new ArrayList<>();
        listFirst.add(1);
        lists.add(listFirst);

        if (numRows == 1) {
            return lists;
        }

        for (int i = 0; i < numRows-1; i++) { //行

            List<Integer> list = new ArrayList<>();
            //每一行的第一个和最后一个元素都是1
            list.add(1);
            if (i > 0) {
                //当前下标1元素来源于上一行的下标0和下标1的和 这里的循环是循环中间一共有多少个元素
                for (int j = 0; j < i; j++) { //列
                    //上一行的元素 [1,2,1]
                    List<Integer> list1 = lists.get(lists.size() - 1);
                    //依次求和,0+1 1+2 2+3
                    list.add(list1.get(j) + list1.get(j + 1));
                }
            }
            //当前下标2元素来源于上一行的下标1和小标2的和。
            list.add(1);
            lists.add(list);
        }
        return lists;
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值