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

一、汉诺塔

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<Integer>>,用于存储所有行的元素。
  • 状态转移:
    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;
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 笔记更新于2019年11月10日,摘要:函数的定义与调用;函数的参数;默认参数;可变参数;关键字参数;命名关键字参...
    三贝_阅读 520评论 0 0
  • 递归 递归就是程序自己调用自己的过程。本身理解递归的思想比较容易,举一个求阶乘的例子: 测试: 实际上递归程序不可...
    何物昂阅读 1,631评论 0 1
  • 我的博客:递归之汉诺塔问题 一.起源: 汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的...
    taylar_where阅读 904评论 1 3
  • 汉诺塔算法 要想利用递归函数解决问题,一定要完成两个基本的要素:递归的终止条件,递推公式。为了分析得到递归函数,下...
    LeeLom阅读 5,508评论 7 7
  • 1.栈 First In Last Out,顺序栈和链栈,六种方法,声明使用方式。 1.1 概论 栈,是一个先进先...
    KaelQ阅读 1,142评论 0 3