目录
以下摘自leetcode Top100精选题目--多维动态规划篇
以下摘自leetcode Top100精选题目--多维动态规划篇
不同路径
一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。
问总共有多少条不同的路径?
示例:
输入:m = 3, n = 7
输出:28
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下
Solution:
典型的动态规划问题,可以通过构建一个二维DP数组来解决这个问题。DP数组的每一个元素dp[i][j]表示到达网格(i,j)的不同路径数量。由于机器人只能向下或者向右移动,所以到达每一个网格的不同路径数量等于到达它左边的网格的不同路径数量加上到达它上边的网格的不同路径数量。
class Solution {
public int uniquePaths(int m, int n) {
// 创建一个二维DP数组,其中dp[i][j]表示到达网格(i,j)的不同路径数量
int[][] dp = new int[m][n];
// 初始化第一行和第一列的DP数组,因为到达第一行和第一列的每一个网格的不同路径数量都为1
for (int i = 0; i < m; i++) {
dp[i][0] = 1;
}
for (int j = 0; j < n; j++) {
dp[0][j] = 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 - 1][n - 1];
}
}
最小路径和
给定一个包含非负整数的 m x n
网格 grid
,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
Solution:
通过构建一个二维DP数组来解决这个问题。DP数组的每一个元素dp[i][j]表示到达网格(i,j)的最小路径和。由于只能向下或者向右移动,到达每一个网格的最小路径和等于到达左边的网格的最小路径和与到达上边的网格的最小路径和中的较小者,再加上这个网格的值。
class Solution {
public int minPathSum(int[][] grid) {
// 获取网格的行数和列数
int m = grid.length;
int n = grid[0].length;
// 创建一个二维DP数组,其中dp[i][j]表示到达网格(i,j)的最小路径和
int[][] dp = new int[m][n];
// 初始化DP数组的第一行和第一列,因为到达第一行和第一列的每一个网格的最小路径和就是这个网格的值加上到达它左边的网格或上边的网格的最小路径和
dp[0][0] = grid[0][0];
for (int i = 1; i < m; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1; j < n; j++) {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
// 遍历每一个网格
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
// 到达当前网格的最小路径和等于到达它左边的网格的最小路径和与到达它上边的网格的最小路径和中的较小者,再加上这个网格的值
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
// 返回到达右下角网格的最小路径和
return dp[m - 1][n - 1];
}
}
最长回文子串
给你一个字符串 s
,找到 s
中最长的 回文子串。
示例:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
输入:s = "cbbd"
输出:"bb"
Solution:
public class Solution {
public String longestPalindrome(String s) {
// 获取字符串的长度
int len = s.length();
// 如果字符串长度小于2,直接返回原字符串
if (len < 2) {
return s;
}
// 初始化最长回文子串的长度和起始位置
int maxLen = 1;
int begin = 0;
// 创建一个二维DP数组,其中dp[i][j]表示s[i...j]是否是回文串
boolean[][] dp = new boolean[len][len];
// 初始化:所有长度为 1 的子串都是回文串
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
// 将字符串转换为字符数组,便于操作
char[] charArray = s.toCharArray();
// 递推开始
// 先枚举子串长度
for (int L = 2; L <= len; L++) {
// 枚举左边界,左边界的上限设置可以宽松一些
for (int i = 0; i < len; i++) {
// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
int j = L + i - 1;
// 如果右边界越界,就可以退出当前循环
if (j >= len) {
break;
}
// 如果两端的字符不相等,那么s[i...j]不是回文串
if (charArray[i] != charArray[j]) {
dp[i][j] = false;
} else {
// 如果子串长度小于3,那么s[i...j]是回文串
if (j - i < 3) {
dp[i][j] = true;
} else {
// 如果s[i+1...j-1]是回文串,那么s[i...j]也是回文串
dp[i][j] = dp[i + 1][j - 1];
}
}
// 只要 dp[i][j] == true 成立,就表示子串 s[i..j] 是回文,
// 此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
// 返回最长回文子串
return s.substring(begin, begin + maxLen);
}
}
最长公共子序列
给定两个字符串 text1
和 text2
,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0
。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"
是"abcde"
的子序列,但"aec"
不是"abcde"
的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
Solution:
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
// 获取text1和text2的长度
int len1 = text1.length(), len2 = text2.length();
// 创建一个二维DP数组,其中dp[i][j]表示text1的前i个字符和text2的前j个字符的最长公共子序列的长度
int[][] dp = new int[len1 + 1][len2 + 1];
// 遍历text1和text2的每一个字符
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
// 如果text1和text2的当前字符相等
if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
// 那么dp[i][j]等于dp[i - 1][j - 1] + 1
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
// 如果text1和text2的当前字符不相等
// 那么dp[i][j]等于Math.max(dp[i - 1][j], dp[i][j - 1])
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
// 返回text1和text2的最长公共子序列的长度
return dp[len1][len2];
}
}
编辑距离
给你两个单词 word1
和 word2
, 请返回将 word1
转换成 word2
所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
示例:
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
Solution:
可以使用一个二维DP数组来解决这个问题。DP数组的每一个元素dp[i][j]表示word1的前i个字符和word2的前j个字符的最小编辑距离。
class Solution {
public int minDistance(String word1, String word2) {
// 获取word1和word2的长度
int m = word1.length(), n = word2.length();
// 创建一个二维DP数组,其中dp[i][j]表示word1的前i个字符和word2的前j个字符的最小编辑距离
int[][] dp = new int[m + 1][n + 1];
// 初始化DP数组的第一行,表示word1的前i个字符和空字符串的最小编辑距离
for (int i = 0; i <= m; i++) {
dp[i][0] = i;
}
// 初始化DP数组的第一列,表示空字符串和word2的前j个字符的最小编辑距离
for (int j = 0; j <= n; j++) {
dp[0][j] = j;
}
// 遍历word1和word2的每一个字符
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// 如果word1和word2的当前字符相等
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
// 那么dp[i][j]等于dp[i - 1][j - 1],不需要做任何操作
dp[i][j] = dp[i - 1][j - 1];
} else {
// 如果word1和word2的当前字符不相等
// 那么dp[i][j]等于Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1
// dp[i - 1][j]表示删除word1的当前字符,dp[i][j - 1]表示在word2的当前位置插入一个字符,dp[i - 1][j - 1]表示替换word1的当前字符
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
}
}
}
// 返回word1和word2的最小编辑距离
return dp[m][n];
}
}