10. 正则表达式匹配
/**
* * 匹配零个或者多个前面的元素
* . 匹配任意单个元素
*/
class Solution {
public boolean isMatch(String s, String p) {
if (s == null || p == null) return false;
boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
dp[0][0] = true;
for (int i = 1; i <= p.length(); i++) {
if (p.charAt(i - 1) == '*')
// 初始化最左边列, 根据题意 * 不可能是 p的第一个元素,所以这里的 i-2不会越界
dp[0][i] = dp[0][i - 2];
}
// 一列一列的比较,从上到下,这里 pi si 表示的是字符串的个数
for (int si = 1; si <= s.length(); si++) {
for (int pi = 1; pi <= s.length(); pi++) {
// pi字符是 . 或者 pi字符和 si字符一样
if (p.charAt(pi - 1) == '.' || p.charAt(pi - 1) == s.charAt(si - 1))
// dp[si][pi]是否匹配,取决于 pi和 si之前的字符是否匹配
dp[si][pi] = dp[si - 1][pi - 1];
else if (p.charAt(pi - 1) == '*') { // pi个字符是 *
// 如果 * 的前面是 . 或者等于 si字符
if (p.charAt(pi - 2) == '.' || p.charAt(pi - 2) == s.charAt(si - 1))
// 则当前位置是否匹配取决于上面两种情况下任意一种匹配
dp[si][pi] = (dp[si][pi - 2] || dp[si - 1][pi]);
else dp[si][pi] = dp[si][pi - 2]; // pi的前一个字符不是 . 也不和si相等
}
}
}
return dp[s.length()][p.length()];
}
}
64. 最小路径和
class Solution {
public static void main(String[] args) {
int[][] grid = {{1, 3, 1}, {1, 5, 1}, {4, 2, 1}};
System.out.println(minPathSum2(grid));
}
/**
* O(mn)
* O(mn)
* @param grid
* @return
*/
public static int minPathSum(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) return 0;
int m = grid.length; // m行
int n = grid[0].length; // n列
int[][] dp = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 && j == 0)
dp[0][0] = grid[0][0];
else if (i == 0)
dp[i][j] = dp[i][j - 1] + grid[i][j];
else if (j == 0)
dp[i][j] = dp[i - 1][j] + grid[i][j];
else
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[m - 1][n - 1];
}
/**
* O(mn)
* O(n)
* @param grid
* @return
*/
public static int minPathSum2(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) return 0;
int m = grid.length; // m行
int n = grid[0].length; // n列
int[] dp = new int[n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 && j == 0) // 第一个数
dp[j] = grid[0][0];
else if (i == 0) // 第一行,左一个dp加上当前数
dp[j] = dp[j - 1] + grid[i][j];
else if (j == 0) // 第一列,上一个dp加上当前数
dp[j] = dp[j] + grid[i][j];
else // 上一个 和 左一个的较小值 加上当前数
dp[j] = Math.min(dp[j], dp[j - 1]) + grid[i][j];
}
}
return dp[n - 1];
}
/**
* O(mn)
* O(1)
* @param grid
* @return
*/
public static int minPathSum3(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) return 0;
int m = grid.length; // m行
int n = grid[0].length; // n列
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 && j == 0)
grid[0][0] = grid[0][0];
else if (i == 0)
grid[i][j] = grid[i][j - 1] + grid[i][j];
else if (j == 0)
grid[i][j] = grid[i - 1][j] + grid[i][j];
else
grid[i][j] = Math.min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j];
}
}
return grid[m - 1][n - 1];
}
}
70. 爬楼梯
class Solution {
public int climbStairs(int n) {
if (n == 1) return 1;
if (n == 2) return 2;
// 一共有n个数,又因为dp[1]=1
// 所以从1开始使用的,要返回dp[n],所以长度为n+1
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
// 转移方程
dp[i] = dp[i - 1]+ dp[i - 2];
}
return dp[n];
}
// 空间优化
public int climbStairs2(int n) {
if (n == 1) return 1;
if (n == 2) return 2;
// 对于第n层来说,只需要知道到达第 n-1,和第 n-2层一共有多少种走法
// 所以只需要用两个值来记录这两个数即可
int prev = 1;
int cur = 2;
for (int i = 3; i <= n; i++) {
// 转移方程
cur = cur + prev;
prev = cur - prev;
}
return cur;
}
}
120. 三角形的最小路径和
方法一 (自底向上)
class Solution {
/**
* dp: 自底向上
* https://leetcode-cn.com/problems/triangle/solution/javadong-tai-gui-hua-si-lu-yi-ji-dai-ma-shi-xian-b/
* @param triangle
* @return
*/
public static int minimumTotal(List<List<Integer>> triangle) {
// 特判
if (triangle == null || triangle.size() == 0) {
return 0;
}
// 加1可以不用初始化最后一行, 因为多加了一行在最后面
// 根据题意,每一行的行数和每一行数据个数相同,按照最大值创建dp数组
int[][] dp = new int[triangle.size() + 1][triangle.size() + 1];
for (int i = triangle.size() - 1; i >= 0; i--) {
List<Integer> rows = triangle.get(i);
for (int j = 0; j < rows.size(); j++) {
// 当前位置[i][j]到 bottom的最小路径和 = 左边路径和 与 右边路径和的较小值
dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + rows.get(j);
}
}
return dp[0][0];
}
}
空间优化
/**
* 空间优化 O(n) n为三角形总行数(其实是最底层那行,数据的个数两者相等)
* @param triangle
* @return
*/
public static int minimumTotalOptimize(List<List<Integer>> triangle) {
// 特判
if (triangle == null || triangle.size() == 0) return 0;
int row = triangle.size();
// 在最后一行的时候,计算dp[3] = Math.min(dp[3], dp[3+1]) + ...
int[] dp = new int[row + 1]; // 创建的dp[]最大程度为: size+1
for (int i = row - 1; i >= 0 ; i--) { // 从最后一行开始遍历
for (int j = 0; j <= i; j++) {
dp[j] = Math.min(dp[j], dp[j+1]) + triangle.get(i).get(j);
}
}
return dp[0];
}
方法二 (自顶向下)
class Solution5 {
public static void main(String[] args) {
/**
* dp: 自顶向下
* https://leetcode-cn.com/problems/triangle/solution/javadong-tai-gui-hua-si-lu-yi-ji-dai-ma-shi-xian-b/
* @param triangle
* @return
*/
public static int minimumTotal(List<List<Integer>> triangle) {
// 特判
if (triangle == null || triangle.size() == 0) {
return 0;
}
int row = triangle.size(); // 三角形行数
int column = triangle.get(row - 1).size(); // 最后一行的列数
// 记录包含 [i][j]元素的最小路径和
int[][] dp = new int[row][column];
// 初始化第一个
dp[0][0] = triangle.get(0).get(0);
int res = Integer.MAX_VALUE;
// 遍历行,从第二行开始(row = 2)
for (int i = 1; i < row; i++) {
// 对每一行的元素进行推导,第几行就有几个元素
for (int j = 0; j <= i; j++) {
if (j == 0) { // 最左端特殊处理
dp[i][j] = dp[i - 1][j] + triangle.get(i).get(j);
} else if (j == i) { // 最右端特殊处理
dp[i][j] = dp[i - 1][j - 1] + triangle.get(i).get(j);
} else {
dp[i][j] = Math.min(dp[i - 1][j], dp[i - 1][j - 1]) + triangle.get(i).get(j);
}
}
}
// 遍历完之后,dp的最后一行保存了三角形最后一行每个数据的最小路径和,找出其中最小的
for (int i = 0; i < column; i++) {
res = Math.min(res, dp[row - 1][i]);
}
return res;
}
}
空间优化
/**
* 对第i行的最小路径和的推导,只需要第 i-1行的 dp[i - 1][j]和 dp[i - 1][j - 1]元素即可。可以使用两个变量暂存。
* 一维的dp数组只存储第i行的最小路径和。
*
* @param triangle
* @return
*/
public static int minimumTotalOptimize(List<List<Integer>> triangle) {
// 特判
if (triangle == null || triangle.size() == 0) {
return 0;
}
int row = triangle.size(); // 三角形行数
int[] dp = new int[row]; // 以最大的行数创建一个一维数组
// 初始化第一个
dp[0] = triangle.get(0).get(0);
// prev用来存放dp[i - 1][j-1], cur用来存放dp[i - 1][j],即 [i][j] 的两个父元素的dp
int prev = 0, cur;
// 遍历行,从第二行开始(row = 2)
for (int i = 1; i < triangle.size(); i++) {
// 从第二行开始对每一行的元素进行推导
List<Integer> rows = triangle.get(i);
// 循环当前层的元素
for (int j = 0; j <= i; j++) {
cur = dp[j];
if (j == 0) {
// 最左端特殊处理:dp[i - 1][j]
dp[j] = cur + rows.get(j);
} else if (j == i) {
// 最右端特殊处理:dp[i - 1][j - 1]
dp[j] = prev + rows.get(j);
} else {
dp[j] = Math.min(cur, prev) + rows.get(j);
}
prev = cur;
}
}
int res = Integer.MAX_VALUE;
// dp最后一行记录了最小路径
for (int i = 0; i < triangle.size(); i++) {
res = Math.min(res, dp[i]);
}
return res;
}