【搞定左神算法初级班】第7节:暴力递归、动态规划 冷不防 2022-04-23 15:20 244阅读 0赞 ### **目 录:** ### 一、递归 题目1:求 n! 的结果 题目2:汉诺塔问题 题目3:打印一个字符串的全部子序列,包括空字符串 题目4:打印一个字符串的全部排列 题目5:母牛每年生一只母牛,新出生的母牛成长三年后也能每年生一只母牛,假设不会死。求N年后,母牛的数量 二、动态规划 动态规划的特点 如何把暴力递归套路变为动态规划 题目1:矩阵最小路径和 题目2:(背包问题)从数组任意选择数字,能不能累加得到 aim -------------------- ## 一、递归 ## * ### 暴力递归的步骤: ### * 1:把问题转化为规模缩小了的同类问题的子问题; * 2:有明确的不需要继续进行递归的条件(base case); * 3:有得到了子问题的结果之后的决策过程; * 4:**不记录每一个子问题的解。** 递归其实就是不断的尝试,不知道明确的计算方式,但是明白怎么去试。 ### 题目1:求 n! 的结果 ### 用递归去求解时:很明显求解 n! 其实就是求解 (n - 1)! 的问题,即它的子问题..... package com.offer.foundation.class6; /** * @author pengcheng * @date 2019/3/30 - 21:27 * @content: n! 问题 */ public class Factorial { // 非递归版本 public long getFactorial1(int n){ long res = 1L; for (int i = 1; i <= n; i++) { res *= i; } return res; } // 递归版本 public long getFactorial2(int n){ if(n == 1){ return 1L; } return (long) n * getFactorial2(n - 1); } // 测试 public static void main(String[] args) { Factorial factorial = new Factorial(); System.out.println(factorial.getFactorial1(5)); // 120 System.out.println(factorial.getFactorial2(5)); // 120 } } ### 题目2:汉诺塔问题 ### * 打印 n 层汉诺塔从最左边移动到最右边的全部过程: > 题目:在一根柱子上从下往上按照大小顺序摞着 n 片黄金圆盘。把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。打印出移动次数最少的全过程。 [![68747470733a2f2f696d61676573302e636e626c6f67732e636f6d2f626c6f672f3334313539332f3230313330392f32333039353131312d36303138353930666364373234316563386639633338383137353234386537642e706e67][]][68747470733a2f2f696d61676573302e636e626c6f67732e636f6d2f626c6f672f3334313539332f3230313330392f32333039353131312d36303138353930666364373234316563386639633338383137353234386537642e706e67] * 【分析】:给三根柱子分别命名为 “left”、“mid”、“right”,from 代表此次需要移动的圆盘所在的位置,to 代表这些圆盘要去的地方,help 是用于辅助的,分三步走: * 1、n-1 个圆盘从 from 到 help; * 2、第 n 个圆盘从 from 到 to; * 3、把那 n-1个圆盘从 help 移动到 to 上面来。 * 时间复杂度:f(n) = 2f(n-1) +1,是2(n-1) **把尝试的能力写成代码就是递归的过程。** package com.offer.foundation.class6; /** * @author pengcheng * @date 2019/3/30 - 22:18 * @content: 汉诺塔问题 */ public class Hanoi { public void hanoi(int n){ if(n > 0){ hanoi(n, "left", "right", "mid"); } } /** * @param n :n个数 * @param from :原位置 * @param help :辅助位置 * @param to : 目标位置 */ public void hanoi(int n, String from, String to, String help){ if(n == 1){ // 只有一个时,直接移到目标位置即可 System.out.println(n + ":" + from + "->" + to); return; } // 下面是处理这个过程的递归问题,只用考虑当前n问题就行,不用尝试去理解它的子问题 hanoi(n - 1, from, help, to); // 第1步:将n-1个圆盘从原位置移动到辅助位置 System.out.println(n + ":" + from + "->" + to); // 第2步:将第n个圆盘移到目标位置,即打印即可 hanoi(n - 1, help, to, from); // 第3步:将位置上的n-1个元素移到到目标位置 } } ### 题目3:打印一个字符串的全部子序列,包括空字符串 ### * **每个结点 i:有 要 和 不要 两种选择,之后的随意选择要或不要。** > * 子序列顺序不能变 > > 输入: > > abc > > 输出: > // 第一个是空串 > c > b > bc > a > ac > ab > abc ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Bjd2wxMjA2_size_16_color_FFFFFF_t_70][] package com.offer.foundation.class6; /** * @author pengcheng * @date 2019/3/31 - 15:19 * @content: 打印一个字符串的全部子序列,包括空字符串 */ public class PrintAllSubString { public void printAllSub(String str){ if(str == null){ return; } char[] chars = str.toCharArray(); if(chars.length > 0){ String pre = new String(""); // pre:表示从0到i-1位置上形成的结果 printAllSub(0, pre, chars); }else{ System.out.println(""); // 输入空字符串也会打印空 } } public void printAllSub(int i, String pre, char[] chars){ // 已经到数组最后一个字符了,所有的选择都做完了,该返回了 if(i == chars.length){ System.out.println(pre); return; } // 如果没有到最后一个字符,那么当前字符两种选择:选择要或者选择不要 printAllSub(i + 1, pre, chars); // 不要当前字符 printAllSub(i + 1, pre + String.valueOf(chars[i]), chars); // 要当前字符 } // 测试 public static void main(String[] args) { PrintAllSubString p = new PrintAllSubString(); String str = "abc"; p.printAllSub(str); } } ### 题目4:打印一个字符串的全部排列 ### 4.1 打印一个字符串的全部排列【每个结点i:有i~n-1种选择,之后的随意排序】 * 你也可以同题目三一样用pre,思想是一样的,这里的i有 n-i 总选择,而题目三因为求的是子序列,只有 2 种选择【要或者不要】。 * 差别:题目三不是所有字母都在,而且字母建不能乱序,所以不能用打印chars这种方法,而要用额外的pre来记录。 package com.offer.foundation.class6; /** * @author pengcheng * @date 2019/4/4 - 16:00 * @content: */ public class PrintAllSort { public static void printAllSort(String string){ if(string == null){ return; } char[] chars = string.toCharArray(); if(chars.length > 0){ func(0, chars); } } // 对i及i以后的字符进行全排序 public static void func(int i, char[] chars){ if(i == chars.length){ System.out.println(String.valueOf(chars)); } for(int j = i; j < chars.length; j++){ swap(i, j, chars); // 第i个位置有i~n-1这些选择 func(i + 1, chars); // 搞第i+1的位置 swap(i, j, chars); } } public static void swap(int i, int j, char[] chars){ char temp = chars[i]; chars[i] = chars[j]; chars[j] = temp; } // 测试 public static void main(String[] args) { printAllSort("abc"); } } 4.2 进阶:打印一个字符串的全部排列,要求不要出现重复的排列 > 什么是不重复的字符串全排列,如果是普通字符串全排列,那么 > > 输入: > > acc > > 输出:【即认为后面两个c是不一样的,4.1的做法】 > > acc > acc > cac > cca > cca > cac > 要求写出的去重的,也就是会输出: > > acc > cac > cca > 【即认为后面两个c是一样的】 * 【分析】:和4.1基本一样,只是增加了一个hashset,用于保证重复字符不会被再次交换。 package com.offer.foundation.class6; import java.util.HashSet; /** * @author pengcheng * @date 2019/4/4 - 16:00 * @content: */ public class PrintAllSort { public static void printAllSort(String string){ if(string == null){ return; } char[] chars = string.toCharArray(); if(chars.length > 0){ func2(0, chars); } } // 对i及i以后的字符进行全排序 public static void func2(int i, char[] chars){ if(i == chars.length){ System.out.println(String.valueOf(chars)); } // 用于保证每次交换的字符不存在重复字符 HashSet<Character> set = new HashSet<>(); for(int j = i; j < chars.length; j++){ // 只有之前没有交换过这个字符才会交换 if(!set.contains(chars[j])) { set.add(chars[j]); swap(i, j, chars); // 第i个位置有i~n-1这些选择 func2(i + 1, chars); // 搞第i+1的位置 swap(i, j, chars); } } } public static void swap(int i, int j, char[] chars){ char temp = chars[i]; chars[i] = chars[j]; chars[j] = temp; } // 测试 public static void main(String[] args) { printAllSort("acc"); } } ### 题目5:母牛每年生一只母牛,新出生的母牛成长三年后也能每年生一只母牛,假设不会死。求N年后,母牛的数量 ### * F(n) = F(n-1) + F(n-3);即今年的牛等于去年的牛加上三年前的牛(因为三年前的牛能够生新牛了) * 对于递归而言,如果找不到规律,可以先分析下小规模问题,看能不能找到规律。本地分析过程如下: <table style="width:500px;"> <thead> <tr> <th style="text-align:center;vertical-align:middle;">第1年</th> <th style="text-align:center;vertical-align:middle;">第2年</th> <th style="text-align:center;vertical-align:middle;">第3年</th> <th style="text-align:center;vertical-align:middle;">第4年</th> <th style="text-align:center;vertical-align:middle;">第5年</th> <th style="text-align:center;vertical-align:middle;">第6年</th> </tr> <tr> <th style="text-align:center;vertical-align:middle;">1</th> <th style="text-align:center;vertical-align:middle;">2</th> <th style="text-align:center;vertical-align:middle;">3</th> <th style="text-align:center;vertical-align:middle;">4</th> <th style="text-align:center;vertical-align:middle;">6</th> <th style="text-align:center;vertical-align:middle;">9</th> </tr> </thead> <tbody> <tr> <td style="text-align:center;vertical-align:middle;">A</td> <td style="text-align:center;vertical-align:middle;">A</td> <td style="text-align:center;vertical-align:middle;">A</td> <td style="text-align:center;vertical-align:middle;">A</td> <td style="text-align:center;vertical-align:middle;">A</td> <td style="text-align:center;vertical-align:middle;">A</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;">B(A生)</td> <td style="text-align:center;vertical-align:middle;">B(A生)</td> <td style="text-align:center;vertical-align:middle;">B(A生)</td> <td style="text-align:center;vertical-align:middle;">B(A生)</td> <td style="text-align:center;vertical-align:middle;">B(A生)</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;">C(A生)</td> <td style="text-align:center;vertical-align:middle;">C(A生)</td> <td style="text-align:center;vertical-align:middle;">C(A生)</td> <td style="text-align:center;vertical-align:middle;">C(A生)</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;">D(A生)</td> <td style="text-align:center;vertical-align:middle;">D(A生)</td> <td style="text-align:center;vertical-align:middle;">D(A生)</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;">E(A生)</td> <td style="text-align:center;vertical-align:middle;">E(A生)</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;">F(B生)</td> <td style="text-align:center;vertical-align:middle;">F(B生)</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;">G(A生)</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;">H(B生)</td> </tr> <tr> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;"> </td> <td style="text-align:center;vertical-align:middle;">I(C生)</td> </tr> </tbody> </table> * 从上面的表格中可以很明显的看出规律:F(n) = F(n-1) + F(n-3),再稍加分析,也能直观的感受到这个公示的正确性。 * 第n年牛的数目等于第n-1年牛的数目和n-3年牛的数目,因为第n年相比较第n-1年增长的就是第n-3年对应牛的数目,因为第n-3年的牛到了第n年都会生一只小牛。 * 该方法的时间复杂度是:O(N),但是这种公式的递推式都存在O(logN)的解法,这里不再讲了。 package com.offer.foundation.class6; /** * @author pengcheng * @date 2019/3/31 - 17:09 * @content: 母牛数量问题 */ public class CowNum { // 求第n年的牛的数量 public static int cowNum(int n){ if(n == 1){ return 1; } if(n == 2){ return 2; } if(n == 3){ return 3; } return cowNum(n - 1) + cowNum(n - 3); } // 测试 public static void main(String[] args) { int num = cowNum(5); System.out.println(num); } } 进阶:如果每只母牛只能活10年,求N年后,母牛的数量。 * 【分析】 cowNum(n) = cowNum(n-1) + cowNum(n-3) -cowNum(n-10);即今年的牛等于去年的牛加上三年前的牛(因为三年前的牛能够生新牛了),然后再减去十年前的牛。 public static int cowNum2(int n){ if(n <= 3){ return n; }else if(n <= 10){ return cowNum2(n - 1) + cowNum2(n - 3); }else{ return cowNum2(n - 1) + cowNum2(n - 3) + cowNum2(n - 10); } } ## 二、动态规划 ## 动态规划是从basecase往上推得到 n ,而递归是从 n 推到basecase再一个一个的返回来得到 n 的结果) * ### 动态规划的特点: ### * 1,从暴力递归中来 * 2,将每一个子问题的解记录下来,避免重复计算【记录每个子问题的解】 * 3,把暴力递归的过程,抽象成了状态表达 * 4,并且存在化简状态表达,使其更加简洁的可能 * ### 如何把暴力递归套路变为动态规划 ### * 【前提】:问题必须是无后效性问题,即我怎么到达子状态的路径不影响子状态的返回值 * 套路化步骤: * 1)分析可变参数(解空间)【可变参数就是,当参数固定了,返回值(状态)就固定了】,可变参数是几维的就是几维状态表; * 2)确定最终状态(即目标状态); * 3)根据basecase确定确定初始状态; * 4)分析一个普遍位置依赖哪些位置; * 5)根据依赖顺序逆序求整个表。 ### 题目1:矩阵最小路径和 ### > 给你一个二维数组,二维数组中的每个数都是正数,要求从左上角走到右下角,每一步只能向右或者向下。沿途经过的数字要累加起来。返回最小的路径和。 **1、递归版本** * 如果矩阵为 n x n,那么时间复杂度为:O(![2^\{n^\{2\}\}][2_n_2])。 package com.offer.foundation.class6; /** * @author pengcheng * @date 2019/4/4 - 16:41 * @content: 矩阵最小路径和问题 */ public class MinPath { public static int minPath(int[][] matrix){ if(matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0){ return 0; } // 从左上角走到右下角 return walk(matrix, 0, 0); } // 从[i,j]位置走到右下角 public static int walk(int[][] matrix, int i, int j){ if(i == matrix.length - 1 && j == matrix[0].length - 1){ // [i,j]位置已经在右下角了 return 0; } if(i == matrix.length - 1){ // [i,j]在矩阵的最后一行,所以只能往右走了 return matrix[i][j] + walk(matrix, i, j + 1); } if(j == matrix[0].length - 1){ // [i,j]在矩阵的最后一列,所以只能往下走了 return matrix[i][j] + walk(matrix, i + 1, j); } int right = walk(matrix, i, j + 1); int down = walk(matrix, i + 1, j); return matrix[i][j] + Math.min(right,down); } } **2、动态规划版本** 递归版本虽然简单,但是时间复杂度过高,显然是不行的。通过分析发现,在递归过程中,会有很多重复的计算,如下图所示: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Bjd2wxMjA2_size_16_color_FFFFFF_t_70 1][] 在计算(1,0)位置的右元素和计算(0,1)位置的下元素时,发生了重复计算:都是计算(1,1)位置到右下角的最小距离和。这里只是分析了两步,如果继续分析,会出现很多类似的重复计算过程。 > **1、无后效性**:无论(1,1)位置是从(1,0)位置来的还是(0,1)位置来的,都不影响(1,1)位置到右下角的最小距离的结果,这就叫做无后效性,反之则是有后效性。 > > 2、无后效性一定可以改成递归版本。 > > 3、汉诺塔问题:每步需要打印出轨迹,所以是有后效性的。 > > 4、八皇后问题:前一步的选择会影响后一步的结果,是有后效性的。 那么我们是不是可以利用缓存将每次的计算结果存储起来,下一次再碰到相同元素计算的时候先去缓存中查找看是否已经计算过了,如果存在则直接使用,在没有计算过的时候再去计算,并将结果存储到缓存中。很明显这样的缓存可以用map实现,元素对应key,结果对应value。 * 改递归思路: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Bjd2wxMjA2_size_16_color_FFFFFF_t_70 2][] 利用basecase(即:i == matrix.length - 1 && j == matrix\[0\].length - 1)可以直接得出图中状态表右下角的位置为6,然后再由6推出最后一行和最右一列的状态值,然后又可以利用刚才推出的值进行新的一轮推到.....最终将整个表的每个位置都填上其对应的状态值。如上图所示:左上角位置状态值为17,即代表从左上角到右下角位置最短路径值为:17。 这个过程就盖楼一样,从地基开始,上层依赖下层。下层盖好了,上层就可以盖了。 package com.offer.foundation.class6; import java.util.HashMap; /** * @author pengcheng * @date 2019/4/4 - 16:41 * @content: 矩阵最小路径和问题 */ public class MinPath { // 动态规划版本 public static int walkDynamic(int[][] matrix){ if(matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0){ return 0; } int lastRow = matrix.length - 1; int lastCol = matrix[0].length - 1; int[][] dp = new int[lastRow][lastCol]; // 状态表 dp[lastRow][lastCol] = matrix[lastRow][lastCol]; // basecase:右下角到右下角的距离为其本身大小 // 填充最后一行其他位置处的状态值 for(int i = lastRow, j = lastCol - 1; j >= 0; j--){ // 左边位置的值等于右边位置值加上它自身的值 dp[i][j] = matrix[i][j] + dp[i][j + 1]; } // 填充最后一列其他位置处的状态值 for(int j = lastCol, i = lastRow - 1; i >= 0; i--){ // 上面的位置等于下面的位置值加上它本身的值 dp[i][j] = matrix[i][j] + dp[i + 1][j]; } // 填充一般位置(除最后一行和最右一列的位置) for(int i = lastRow - 1; i >=0; i--){ for(int j = lastCol - 1; j >= 0; j--){ // 一般位置:当前位置值 + min(下面位置值,右面位置值) dp[i][j] = matrix[i][j] + Math.min(dp[i + 1][j],dp[i][j + 1]); } } return dp[0][0]; // 返回目标值 } } ### 题目2:(背包问题)从数组任意选择数字,能不能累加得到 aim ### > 给你一个数组 arr,和一个整数 aim。如果可以任意选择 arr 中的数字,能不能累加得到 aim,返回 true 或者 false。 **1、递归版本** * 【分析】:每个位置 i 有 要和不要 两种选择;叶节点会看自己这里的结果是不是 aim,从而向父结点返回 true 或 false,父结点比较子节点的结果,有一个为 true 就一直返回 true,否则返回 false。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Bjd2wxMjA2_size_16_color_FFFFFF_t_70 3][] 如上图所示:数组 arr = \{3, 2, 5\} ,aim = 7: f(0, 0):代表0位置处状态值为0的点; f(2, 5):代表2位置处状态值为5的点。 只要有叶节点的值等于 aim 的值,则会返回 true。 package com.offer.foundation.class6; /** * @author pengcheng * @date 2019/4/4 - 19:27 * @content: */ public class SumToAim { public static boolean IsSumToAim(int[] arr, int aim){ if(arr == null){ return false; } return process(arr, 0, 0, aim); } // pre:是 0 ~ (i - 1)随意相加产生的结果 // 用于判断pre+i及其后面的数字随意相加,是否能够得到aim public static boolean process(int[] arr, int i, int pre, int aim){ if(i == arr.length){ return pre == aim; } // 位置i有两种选择:要或不要,有一个等于aim,即返回true return process(arr, i + 1, pre, aim) || process(arr, i + 1, pre + arr[i], aim); } } **2、动态规划版本** 1. 判断是否为无后效性:是无后效性的; 2. 确定可变参数:i 值,sum 值,aim 值是固定的; 3. 确定二维状态表(两个可变参数)。 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Bjd2wxMjA2_size_16_color_FFFFFF_t_70 4][] 状态表如上图所示,横坐标为 m 的值,纵坐标为 i 的值。从 basecase 可以看出最后一行的状态值是可以确定的,所以从最后一行往上推导,一直推导到左上角的位置处,如果为 True,则返回 True(图中空白处都为false)。 怎么通过下面一行的状态值得出上面一行的状态值呢?看递归的代码: process(arr, i + 1, pre, aim) || process(arr, i + 1, pre + arr[i], aim) 因此: 1. i 行为 True 的位置,其对应 i - 1 行正上方位置也为 True; 2. i 行为 True 的位置处的值减去 i - 1 行对应的值,得到的在 sum 范围内的值对应的位置处为 True。 package com.offer.foundation.class6; /** * @author pengcheng * @date 2019/4/4 - 19:27 * @content: */ public class SumToAim { // 递归版本 public static boolean isSumToAim2(int[] arr, int aim){ if(arr == null || arr.length == 0){ return false; } // 状态表:需要注意到底需要几行 boolean[][] dp = new boolean[arr.length + 1][aim + 1]; // 填好最后一行:i为横坐标,pre为纵坐标 for(int i = arr.length, sum = 0; sum <= aim; sum++){ if(sum == aim){ dp[i][sum] = true; // 目标值处设置为true }else{ dp[i][sum] = false; } } // 按照递归填好状态表中的每一个位置:从下一行推导出上一行的状态值 for(int i = arr.length - 1; i >= 0; i--){ for(int sum = aim; sum >= 0; sum--){ if(sum + arr[i] > aim){ dp[i][sum] = dp[i + 1][sum]; }else{ // dp[i][sum]值为true的两种情况:正下方值为true || dp[i+1][sum+arr[i]]的值为true,有一个为ture就行 dp[i][sum] = dp[i + 1][sum] || dp[i + 1][sum + arr[i]]; } } } return dp[0][0]; } } [68747470733a2f2f696d61676573302e636e626c6f67732e636f6d2f626c6f672f3334313539332f3230313330392f32333039353131312d36303138353930666364373234316563386639633338383137353234386537642e706e67]: /images/20220226/773a9adc2887462999dfd05cabbb9eb9.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Bjd2wxMjA2_size_16_color_FFFFFF_t_70]: /images/20220226/3e5e5095879c4f699fd7fc29ea8ccf37.png [2_n_2]: https://private.codecogs.com/gif.latex?2%5E%7Bn%5E%7B2%7D%7D [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Bjd2wxMjA2_size_16_color_FFFFFF_t_70 1]: /images/20220226/3332b5d78ea6468e8cf98fbef4a092a7.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Bjd2wxMjA2_size_16_color_FFFFFF_t_70 2]: /images/20220226/7b56b36f687d49cb8546b99bf8e78212.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Bjd2wxMjA2_size_16_color_FFFFFF_t_70 3]: /images/20220226/7a5f6602deb04540a1b50d57f065f90b.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Bjd2wxMjA2_size_16_color_FFFFFF_t_70 4]: /images/20220226/f54210edfc1a4e0ea7556136867f4565.png
相关 暴力递归:动态规划的雏形 一.暴力递归的基本概念: 1. 什么是暴力递归?简而言之,暴力递归就是尝试,与此同时,暴力递归是动态规划的前身,换句话说:动态规划是对暴力递归的优化。 1. 关于解决暴 Bertha 。/ 2024年03月25日 18:26/ 0 赞/ 70 阅读
相关 算法7.从暴力递归到动态规划0 算法|7.从暴力递归到动态规划0 1.汉诺塔 题意:打印n层汉诺塔从最左边移动到最右边的全部过程 解题思路: 把字母抛掉,变成左中右三个盘子 多个盘 秒速五厘米/ 2024年03月16日 09:44/ 0 赞/ 30 阅读
相关 左神提升6:暴力递归改动态规划 内容 讲述暴力递归和动态规划的关系 =》去重的过程 记忆化搜索 傻缓存 动态规划都可以由暴力递归改进过来,解决动态规划的套路 常见的尝试模型 设计尝试过程的原则 超、凢脫俗/ 2022年09月10日 07:09/ 0 赞/ 177 阅读
相关 从暴力递归到动态规划的转换(推荐) `从暴力递归到动态规划` `` `给一串数字,返回其能否转换成IP地址形式(IP地址的正确形式)。` `如110.125.10.5` `` `int P(i,p) 青旅半醒/ 2022年06月09日 13:21/ 0 赞/ 213 阅读
相关 【搞定左神算法初级班】第7节:暴力递归、动态规划 目 录: 一、递归 题目1:求 n! 的结果 题目2:汉诺塔问题 题目3:打印一个字符串的全部子序列,包括空字符串 题目4:打印一个字符串的全部排列 题目5:母 冷不防/ 2022年04月23日 15:20/ 0 赞/ 245 阅读
相关 【搞定左神算法初级班】第3节:栈、队列、链表、矩阵结构及相关常见面试题 目 录: 一、栈 题目1:用固定的大小的数组实现栈和队列 题目2:能返回栈中最小元素的栈 题目3:如何仅用队列结构实现栈结构? 二、队列 题目1:如何仅用栈结 淡淡的烟草味﹌/ 2022年02月28日 12:48/ 0 赞/ 120 阅读
相关 【搞定左神算法初级班】第4节:二叉树及相关常见面试题 目 录: 题目1:实现二叉树的先序、中序、后序遍历【递归方式和非递归方式】 题目2:在二叉树中找到一个节点的后继节点 题目3:介绍二叉树的序列化和反序列化 题目4: 怼烎@/ 2022年02月27日 13:18/ 0 赞/ 183 阅读
相关 【搞定左神算法初级班】第6节:前缀树、贪心算法 目 录: 一、前缀树:Prefix Tree 1.1 前缀树题目举例:一个字符串类型的数组 arr1,另一个字符串类型的数组 arr2 1.2 前缀树的 insert 以你之姓@/ 2022年02月26日 13:20/ 0 赞/ 187 阅读
相关 左神算法进阶班1_4Manacher算法 1 include <iostream> 2 include <string> 3 4 using namespace std; 朴灿烈づ我的快乐病毒、/ 2022年01月06日 00:01/ 0 赞/ 166 阅读
还没有评论,来说两句吧...