基础算法 - 动态规划 - 2 相关题目 62. 不同路径 - 力扣(LeetCode) 64. 最小路径和 - 力扣(LeetCode) 5. 最长回文子串 - 力扣(LeetCode) 动态规划 中心开花法 1143. 最长公共子序列 - 力扣(LeetCode) 72. 编辑距离 - 力扣(LeetCode)
基础算法 - 动态规划 - 2 多维动态规划。
相关题目 难度:中等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int uniquePaths (int m, int n) { int [][] dp = new int [m + 1 ][n + 1 ]; dp[0 ][1 ] = 1 ; for (int i = 1 ; i <= m; i++) { for (int j = 1 ; j <= n; j++) { dp[i][j] = dp[i - 1 ][j] + dp[i][j - 1 ]; } } return dp[m][n]; } }
爬楼梯二维版。
状态定义
dp[i][j] 表示从起点 (1, 1) 到位置 (i, j) 的路径数量。
状态转移方程
对于位置 (i, j),它的路径数量等于它上方位置 (i-1, j) 和左侧位置 (i, j-1) 的路径数量之和: dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
难度:中等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int minPathSum (int [][] grid) { int m = grid.length; int n = grid[0 ].length; int [][] dp = new int [m + 1 ][n + 1 ]; for (int i = 1 ; i <= m; i++) { for (int j = 1 ; j <= n; j++) { if (i == 1 ) { dp[i][j] = dp[i][j - 1 ] + grid[i - 1 ][j - 1 ]; } else if (j == 1 ) { dp[i][j] = dp[i - 1 ][j] + grid[i - 1 ][j - 1 ]; } else { dp[i][j] = Math.min(dp[i - 1 ][j], dp[i][j - 1 ]) + grid[i - 1 ][j - 1 ]; } } } return dp[m][n]; } }
状态定义
dp[i][j] 表示从起点 (1, 1) 到位置 (i, j) 的最小路径和。
状态转移
对于位置 (i, j),它的最小路径和等于它上方位置 (i-1, j) 和左侧位置 (i, j-1) 的最小路径和加上当前位置的值。
难度:中等
动态规划 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public String longestPalindrome (String s) { int len = s.length(); boolean [][] dp = new boolean [len + 2 ][len + 2 ]; for (int i = 0 ; i <= len + 1 ; i++) Arrays.fill(dp[i], true ); int start = -1 ; int end = -1 ; int maxLen = -1 ; for (int i = len; i >= 1 ; i--) { for (int j = i; j <= len; j++) { dp[i][j] = dp[i + 1 ][j - 1 ] && (s.charAt(i - 1 ) == s.charAt(j - 1 )); if (dp[i][j]) { if (j - i + 1 > maxLen) { maxLen = j - i + 1 ; start = i - 1 ; end = j; } } } } return s.substring(start, end); } }
状态定义:
用一个二维布尔数组 dp[i][j] 来表示从第 i 个字符到第 j 个字符是否是回文子串。
dp[i][j] 为 true 表示 s[i-1...j-1] 是回文子串,false 则表示不是。
初始化:
所有 dp[i][i] 都是 true,因为单个字符是回文。
所有 dp[i][i-n] 也是被设置为true,这是根据状态转移方程需要而设置的。
状态转移方程:
中心开花法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { int left; int right; public String longestPalindrome (String s) { left = 0 ; right = 0 ; char [] charArray = s.toCharArray(); for (int i = 0 ; i < charArray.length-1 ; i++) { valid(charArray,i,i); valid(charArray,i,i+1 ); } return new String (charArray,left,right-left+1 ); } public void valid (char [] charArray , int i ,int j) { while (i>= 0 && j < charArray.length && charArray[i] == charArray[j]){ i--; j++; } i++; j--; if (j - i > right -left){ left = i; right = j; } } }
运行速度比动态规划法要快很多。
难度:中等
这个题的难点在于分析出状态转移方程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int longestCommonSubsequence (String text1, String text2) { int len1 = text1.length(); int len2 = text2.length(); int [][] dp = new int [len1+1 ][len2+1 ]; char [] charArray1 = text1.toCharArray(); char [] charArray2 = text2.toCharArray(); for (int i=1 ; i<=len1; i++) { for (int j=1 ; j<=len2; j++) { if (charArray1[i-1 ] == charArray2[j-1 ]) dp[i][j] = dp[i-1 ][j-1 ] + 1 ; else dp[i][j] = Math.max(dp[i][j-1 ], dp[i-1 ][j]); } } return dp[len1][len2]; } }
定义状态 :
dp[i][j] 表示 text1 的前 i 个字符和 text2 的前 j 个字符的最长公共子序列的长度。
状态转移方程 :
如果 text1[i-1] == text2[j-1],那么 dp[i][j] = dp[i-1][j-1] + 1。
如果 text1[i-1] != text2[j-1],那么 dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j])。
1 2 3 4 text1 = 'abcde' text2 = 'ace' dp[3][2] = dp[2][1]+1; dp[3][3] = max(dp[3][2],dp[2][3]);
难度:中等
本体的难度在于抽象出动态规划的状态和状态转移方程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { public int minDistance (String word1, String word2) { int m = word1.length(); int n = word2.length(); int [][] dp = new int [m + 1 ][n + 1 ]; for (int i = 1 ; i <= m; i++) { dp[i][0 ] = i; } for (int j = 1 ; j <= n; j++) { dp[0 ][j] = j; } char [] charArray1 = word1.toCharArray(); char [] charArray2 = word2.toCharArray(); for (int i = 1 ; i <= m; i++) { char char1 = charArray1[i - 1 ]; for (int j = 1 ; j <= n; j++) { char char2 = charArray2[j - 1 ]; int insert_j = dp[i][j - 1 ] + 1 ; int insert_i = dp[i - 1 ][j] + 1 ; int replace = dp[i - 1 ][j - 1 ]; if (char1 != char2) { replace += 1 ; } dp[i][j] = Math.min(Math.min(insert_j, insert_i), replace); } } return dp[m][n]; } }
初始化 :
dp[i][0] = i:将 word1 的前 i 个字符删除需要 i 步。
dp[0][j] = j:将 word2 的前 j 个字符插入需要 j 步。
状态转移 :
在word2中插入 :dp[i][j-1] + 1 对应在 word2 中插入一个字符,使得前 i 个字符能与 word2 的前 j 个字符匹配。
在word1中插入 : dp[i-1][j] + 1 对应在 word1 中删除一个字符,使得前 i-1 个字符能与 word2 的前 j 个字符匹配。
替换操作 :dp[i-1][j-1] + 1(如果当前字符不相等)或者 dp[i-1][j-1](如果当前字符相等)。
动态规划 :
利用三种操作的最小值更新 dp[i][j],从而得到将 word1 的前 i 个字符转换为 word2 的前 j 个字符所需的最小操作数。
结果 :
最后返回 dp[m][n],即将整个 word1 转换为 word2 所需的最小操作数。