18.从递归入手二维动态规划
前置知识: 讲解038-经典递归过程解析、讲解066-从递归入手一维动态规划
本节课: 讲解从递归到二维动态规划的过程 讲解二维动态规划的空间压缩技巧 讲解哪些递归不适合或者说没有必要改成动态规划
下节课:直接从动态规划的定义入手,来见识更多二维动态规划问题
注意: 二维动态规划问题非常多,不仅讲解067、讲解068涉及,整个系列课程会大量涉及 【必备】课程后续会讲背包dp、区间dp、状压dp等等,依然包含大量二维动态规划问题
尝试函数有1个可变参数可以完全决定返回值,进而可以改出1维动态规划表的实现 同理 尝试函数有2个可变参数可以完全决定返回值,那么就可以改出2维动态规划的实现
一维、二维、三维甚至多维动态规划问题,大体过程都是: 写出尝试递归 记忆化搜索(从顶到底的动态规划) 严格位置依赖的动态规划(从底到顶的动态规划) 空间、时间的更多优化
64. 最小路径和
已解答
中等
给定一个包含非负整数的 *m* x *n*
网格 grid
,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例 1:
输入:grid = [[1,3,1],[1,5,1],[4,2,1]] 输出:7 解释:因为路径 1→3→1→1→1 的总和最小。
static int [][] dp = new int[200][200]; public int minPathSum(int[][] grid) { int n = grid.length; int m = grid[0].length; dp[n-1][m-1] = grid[n-1][m-1]; for(int c = m-2;c>=0;c--){ dp[n-1][c] =grid[n-1][c] + dp[n-1][c+1] ; } for(int r = n-2;r>=0;r--){ dp[r][m-1] = grid[r][m-1]+dp[r+1][m-1]; } for(int r = n-2;r>=0;r--){ for(int c = m-2;c>=0;c--){ dp[r][c] = grid[r][c]+Math.min(dp[r+1][c],dp[r][c+1]); } } return dp[0][0]; }
能改成动态规划的递归,统一特征:
决定返回值的可变参数类型往往都比较简单,一般不会比int类型更复杂。为什么?
从这个角度,可以解释 带路径的递归(可变参数类型复杂),不适合或者说没有必要改成动态规划 题目2就是说明这一点的
不管几维动态规划 经常从递归的定义出发,避免后续进行很多边界讨论 这需要一定的经验来预知
1143. 最长公共子序列
已解答
中等
提示
给定两个字符串 text1
和 text2
,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0
。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
-
例如,
"ace"
是"abcde"
的子序列,但"aec"
不是"abcde"
的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace" 输出:3 解释:最长公共子序列是 "ace" ,它的长度为 3 。
static int[][] dp; public static int longestCommonSubsequence(String text1, String text2) { char [] s1 = text1.toCharArray(); char [] s2 = text2.toCharArray(); int n = s1.length; int m = s2.length; int [][]dp = new int[n+1][m+1]; if (s1[0]==s2[0]){ dp[0][0]=1; } for (int i = 1;i<n;i++){ if (s1[i]==s2[0]){ dp[i][0]=1; }else { dp[i][0]=dp[i-1][0]; } } for (int i = 1;i<m;i++){ if (s2[i]==s1[0]){ dp[0][i]=1; }else { dp[0][i]=dp[0][i-1]; } } for (int row = 1;row<n;row++){ for (int col = 1;col<m;col++){ int p1 = dp[row-1][col-1]; int p2 = 0; int p3 = 0; if(s1[row]==s2[col]){ p1 += 1; }else { p2 = dp[row-1][col]; p3 = dp[row][col-1]; } dp[row][col] = Math.max(p1,Math.max(p2,p3)); } } return dp[n-1][m-1]; }
516. 最长回文子序列
已解答
中等
给你一个字符串 s
,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = "bbbab" 输出:4 解释:一个可能的最长回文子序列为 "bbbb" 。
public static int longestPalindromeSubseq(String s) { char [] chars = s.toCharArray(); int l = 0; int r = s.length()-1; int [][]dp = new int[r+1][r+1]; for(int i = 0;i<=r;i++){ dp[i][i] = 1 ; } for(int row = r-1;row>=0;row--){ for(int col = row+1;col<r+1;col++){ //当前两种情况 首位字符是否相等 if(chars[row]==chars[col]){ dp[row][col] = 2+dp[row+1][col-1]; }else{ int p1 = Math.max(dp[row+1][col],dp[row][col-1]); dp[row][col] = p1; } } } return dp[0][r]; }
329. 矩阵中的最长递增路径
已解答
困难
给定一个 m x n
整数矩阵 matrix
,找出其中 最长递增路径 的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你 不能 在 对角线 方向上移动或移动到 边界外(即不允许环绕)。
示例 1:
输入:matrix = [[9,9,4],[6,6,8],[2,1,1]] 输出:4 解释:最长递增路径为 [1, 2, 6, 9]。
static int[][] dp ; static int [] move = {1,0,-1,0,1}; static int n; static int m; public static int longestIncreasingPath(int[][] matrix) { n = matrix.length; m = matrix[0].length; int max = 0; dp = new int[n][m]; for(int i = 0;i<n;i++){ Arrays.fill(dp[i],-1); } for(int i = 0;i<n;i++){ for(int j = 0;j<m;j++){ max = Math.max(max,process(matrix,i,j)); } } return max; } public static int process(int [][]matrix,int row ,int col){ if(dp[row][col]!=-1){ return dp[row][col]; } int ans = 1; for(int i = 1;i<move.length;i++){ int nrow = row+move[i-1]; int ncol = col+move[i]; if(nrow>=0&&ncol>=0&&ncol<m&&nrow<n&&matrix[nrow][ncol]>matrix[row][col]){ ans = Math.max(ans,process(matrix,nrow,ncol)+1); } } dp[row][col] = ans; return ans; }
二叉树
-
题目
-
题解(2)
-
讨论(16)
-
排行
时间限制:1秒 空间限制:128M
知识点Java工程师C++工程师PHP工程师golang工程师前端工程师安卓工程师iOS工程师算法工程师大数据开发工程师信息技术岗运维工程师安全工程师数据分析师数据库工程师游戏研发工程师区块链测试开发工程师测试工程师阿里巴巴2021
校招时部分企业笔试将禁止编程题跳出页面,为提前适应,练习时请使用在线自测,而非本地IDE。
描述
小强现在有𝑛n个节点,他想请你帮他计算出有多少种不同的二叉树满足节点个数为𝑛n且树的高度不超过𝑚m的方案.因为答案很大,所以答案需要模上1e9+7后输出. 树的高度: 定义为所有叶子到根路径上节点个数的最大值.
例如: 当n=3,m=3时,有如下5种方案:
数据范围:1≤𝑛,𝑚≤50 1≤n,m≤50
进阶:时间复杂度𝑂(𝑚𝑛2) O(m**n2) ,空间复杂度𝑂(𝑛𝑚) O(n**m)
输入描述:
第一行输入两个正整数𝑛n和𝑚m. 1≤𝑚≤𝑛≤501≤m≤n≤50
输出描述:
输出一个答案表示方案数.
示例1
输入:
3 3
复制
输出:
5
import java.util.Arrays; import java.util.Scanner; import java.io.*; // 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main { static int MOD = 1000000007; static int [][] dp; public static void main(String[] args)throws IOException { BufferedReader bf = new BufferedReader( new InputStreamReader(System.in)); // 注意 hasNext 和 hasNextLine 的区别 String [] s = bf.readLine().split(" "); int n = Integer.parseInt(s[0]); int m = Integer.parseInt(s[1]); dp = new int [n+1][m+1]; for (int i = 0;i<=m;i++){ dp[0][i] = 1; } for (int row = 1;row<n+1;row++){ for (int col = 1;col<m+1;col++){ long ans = 0; for (int k = 0; k < n+1; k++) { if(row-k-1<0){ break; } ans = (ans + ((long) dp[k][col - 1] * dp[row - k - 1][ col - 1]) % MOD) % MOD; } dp[row][col] = (int) ans; } } PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); out.print(dp[n][m]); out.close(); } /**返回高度为m,节点数为n的二叉树有多少种 */ public static int process(int n,int m){ //如果此时不剩下节点,说明得到一种 if(n==0){ return 1; } if(m==0){ return 0; } if(dp[n][m]!=-1){ return dp[n][m]; } long ans = 0; // n个点,头占掉1个 for (int k = 0; k < n; k++) { // 一共n个节点,头节点已经占用了1个名额 // 如果左树占用k个,那么右树就占用i-k-1个 ans = (ans + ((long) process(k, m - 1) * process(n - k - 1, m - 1)) % MOD) % MOD; } dp[n][m] =(int) ans; return (int) ans; } }
前置知识: 从递归入手二维动态规划
本节课不再从递归入手,而是直接从动态规划的定义入手,来见识更多二维动态规划问题
本节课包含一些 比较巧妙的尝试思路
注意: 二维动态规划问题非常多,整个系列课程会大量涉及 【必备】课程后续会讲背包dp、区间dp、状压dp等等,依然包含大量二维动态规划问题
115. 不同的子序列
已解答
困难
给你两个字符串 s
和 t
,统计并返回在 s
的 子序列 中 t
出现的个数,结果需要对 109 + 7 取模。
示例 1:
输入:s = "rabbbit", t = "rabbit" 输出:3 解释: 如下所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。 rabbbit rabbbit rabbbit
static int[][] dp ; public static int numDistinct(String s, String t) { //定义转移方程 dp[i][j]表示 在到s[i]位置之前所有的子序列满足t的前j(不包含j)个字符的数量 char[] chars = s.toCharArray(); char[] chars1 = t.toCharArray(); int n = chars.length; int m = t.length(); dp = new int[n+1][m+1]; //当 j = 0的时候,默认空串是不是就能解决 for (int i = 0;i<n+1;i++){ dp[i][0] = 1; } //当前 i j位置,分两种情况,如果 s字符串i位置的字符等于t字符串j位置的字符 //dp[i][j] = dp[i-1][j-1]+dp[i-1][j] //如果不等于dp[i][j] = dp[i-1][j] for (int i = 1;i<n+1;i++){ for (int j = 1;j<=i&&j<m+1;j++){ dp[i][j]+=dp[i-1][j]; if (chars1[j-1]==chars[i-1]){ dp[i][j] += dp[i-1][j-1]; } } } return dp[n][m]; }
72. 编辑距离
已解答
中等
给你两个单词 word1
和 word2
, 请返回将 word1
转换成 word2
所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
-
插入一个字符
-
删除一个字符
-
替换一个字符
示例 1:
输入:word1 = "horse", word2 = "ros" 输出:3 解释: horse -> rorse (将 'h' 替换为 'r') rorse -> rose (删除 'r') rose -> ros (删除 'e')
public static int minDistance(String word1, String word2) { //dp[i][j]代表 当前使word1 == word2所做的的操作数 char[] chars1 = word1.toCharArray(); char[] chars2 = word2.toCharArray(); int n = chars1.length; int m = chars2.length; int [][] dp = new int[n+1][m+1]; //本身就等于dp[i-1][j]+1; //if(word1[i-1] == word[j-1]) 当前字符就等于 dp[i][j] = dp[i-1][j-1] //如果不相等 dp[i][j] = dp[i-1][j-1]+1; for(int i = 0;i<=n;i++){ dp[i][0]=i; } for (int i = 1;i<=m;i++){ dp[0][i] = i; } for (int i = 1;i<=n;i++){ for (int j = 1;j<=m;j++){ int p1 = dp[i-1][j]+1; int p2 = dp[i][j-1]+1; if (chars1[i-1]==chars2[j-1]){ p1 = Math.min( dp[i-1][j-1],p1); }else{ p1 = Math.min(dp[i-1][j-1]+1,p1); } dp[i][j] = Math.min(p1,p2); } } return dp[n][m]; }
已解答
中等
相关标签
相关企业
给定三个字符串 s1
、s2
、s3
,请你帮忙验证 s3
是否是由 s1
和 s2
交错 组成的。
两个字符串 s
和 t
交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空
子字符串
:
-
s = s1 + s2 + ... + sn
-
t = t1 + t2 + ... + tm
-
|n - m| <= 1
-
交错 是
s1 + t1 + s2 + t2 + s3 + t3 + ...
或者t1 + s1 + t2 + s2 + t3 + s3 + ...
注意:a + b
意味着字符串 a
和 b
连接。
示例 1:
输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac" 输出:true
public static boolean isInterleave(String s1, String s2, String s3) { int l1 = 0; int l2 = 0; int l3 = 0; int n = s1.length(); int m = s2.length(); int q = s3.length(); char [] chars1 = s1.toCharArray(); char [] chars2 = s2.toCharArray(); char [] chars3 = s3.toCharArray(); if(n+m!=q){ return false; } boolean [][] dp = new boolean[q+1][q+1]; for (int i = 0;i<=q;i++){ dp[i][q] = true; } for (int i = q;i>=0;i--){ for (int j = q-1;j>=0;j--){ boolean flag1 = false; boolean flag2 = false; if(i<chars1.length&&chars1[i]==chars3[j]){ flag1 = dp[i+1][j+1]; } if(j-i>=0&&j-i<chars2.length&&chars2[j-i]==chars3[j]){ flag2 = dp[i][j+1]; } dp[i][j] = flag1||flag2; } } return dp[0][0]; }
明天更新三维动态规划,持续关注