文章目录
动态规划入门
斐波那契数
力扣相关题目:
509.斐波那契数
70.爬楼梯
746.使用最小花费爬楼梯
Java代码:
class Solution {
// 509.斐波那契数
public int fib(int n) {
if (n <= 1) return n;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i ++)
{
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// 70.爬楼梯
public int climbStairs(int n) {
if (n == 1) return 1;
if (n == 2) return 2;
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];
}
// 746.使用最小花费爬楼梯
public int minCostClimbingStairs(int[] cost) {
int n = cost.length;
if (n <= 1) return 0;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 0;
for (int i = 2; i <= n; i ++)
{
dp[i] = Math.min(dp[i - 2] + cost[i - 2], dp[i - 1] + cost[i - 1]);
}
return dp[n];
}
}
路径问题
Java代码:
class Solution {
// 62.不同路径
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for (int i = 0; i < m; i ++) dp[i][0] = 1;
for (int i = 0; i < n; i ++) dp[0][i] = 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];
}
// 63.不同路径2
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length, n = obstacleGrid[0].length;
if (obstacleGrid[0][0] == 1 || obstacleGrid[m - 1][n - 1] == 1) return 0;
int[][] dp = new int[m][n];
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i ++) dp[i][0] = 1;
for (int i = 0; i < n && obstacleGrid[0][i] == 0; i ++) dp[0][i] = 1;
for (int i = 1; i < m; i ++)
{
for (int j = 1; j < n; j ++)
{
if (obstacleGrid[i][j] == 1) continue;
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
}
整数拆分
力扣相关题目:
343.整数拆分
Java代码:
class Solution {
// 343.整数拆分
public int integerBreak(int n) {
int[] dp = new int[n + 1];
// 初始条件:2可以拆成1*1=1
dp[2] = 1;
for (int i = 3; i <= n; i ++)
{
for (int j = 1; j <= i/2; j ++)
{
// 可以拆成两个数i-j和j;也可以拆成j和dp[i-j]
dp[i] = Math.max(dp[i], Math.max((i-j)*j,j*dp[i - j]));
}
}
return dp[n];
}
}
动态规划在树中的应用
力扣相关题目:
96.不同的二叉搜索树
Java代码:
class Solution {
// 96.不同的二叉搜索树
public int numTrees(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
for (int i = 1; i <=n; i ++)
{
for (int j = 1; j <= i; j ++)
{
// 以dp[3]为例:
// dp[3]=元素1为头结点搜索树的数量 + 元素2为头结点搜索树的数量
// + 元素3为头结点搜索树的数量
// 元素1为头结点搜索树的数量 = 右子树有2个元素的搜索树数量 * 左子树有0个元素的搜索树数量
// 元素2为头结点搜索树的数量 = 右子树有1个元素的搜索树数量 * 左子树有1个元素的搜索树数量
// 元素3为头结点搜索树的数量 = 右子树有0个元素的搜索树数量 * 左子树有2个元素的搜索树数量
// 所以 dp[3]=dp[2] * dp[0] + dp[1] * dp[1] + dp[0] * dp[2]
dp[i] += dp[j - 1]*dp[i - j];
}
}
return dp[n];
}
}
背包问题
本篇主要记录01背包和完全背包问题
01背包
你有一个背包,最多能容纳的体积是V,现在有n个物品,第i个物品的体积为 v i v_i vi ,价值为 w i w_i wi,则
(1) 求这个背包至多能装多大价值的物品?
(2) 若背包恰好装满,求至多能装多大价值的物品?
牛客网:
【模板】01背包
Java代码:
import java.util.Scanner;
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
while (in.hasNextInt()) { // 注意 while 处理多个 case
int n = in.nextInt();
int V = in.nextInt();
int[] weight = new int[n];
int[] value = new int[n];
int[] dp = new int[V+1];
for (int i = 0; i < n; i ++)
{
weight[i] = in.nextInt();
value[i] = in.nextInt();
}
// 外循环遍历物品
for (int i = 0; i < n; i ++)
{
// 内循环倒着遍历容量
for (int j = V; j >= weight[i]; j --)
{
// 装该物品:dp[j - weight[i]] + value[i]
// 不装该物品:dp[j]
// 求最大值
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
System.out.println(dp[V]);
int[] dp1 = new int[V+1];
Arrays.fill(dp1,Integer.MIN_VALUE);
dp1[0] = 0;
for (int i = 0; i < n; i ++)
{
for (int j = V; j >= weight[i]; j --)
{
dp1[j] = Math.max(dp1[j], dp1[j - weight[i]] + value[i]);
}
}
if (dp1[V] < 0) dp1[V] = 0;
System.out.println(dp1[V]);
}
}
}
力扣相关题目:
416.分割等和子集
1049.最后一块石头的重量II
494.目标和
474.一和零
Java代码:
class Solution {
// 416.分割等和子集
// 数组中找到子集,该子集总和为sum/2
// 背包容量为sum/2,物品为数组nums,物品重量为nums[i],价值也为nums[i]
// dp[target] == target?
public boolean canPartition(int[] nums) {
int n = nums.length;
if (n < 2) return false;
int sum = 0;
for(int i = 0; i < n; i ++)
{
sum += nums[i];
}
if (sum % 2 == 1) return false;
int target = sum / 2;
// 所以该题等价于背包的容量V=target=sum/2
int[] dp = new int[target + 1];
for (int i = 0; i < n; i ++)
{
for (int j = target; j >= nums[i]; j --)
{
dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);
}
}
if (dp[target] == target) return true;
return false;
}
// 1049.最后一块石头的重量II
// 这道题同样是背包容量为sum/2,与416.分割等和子集思路一致
public int lastStoneWeightII(int[] stones) {
int n = stones.length;
int sum = 0;
for (int i = 0; i < n; i ++)
{
sum += stones[i];
}
int target = sum / 2;
int[] dp = new int[target + 1];
for (int i = 0; i < n; i ++)
{
for (int j = target; j >= stones[i]; j --)
{
dp[j] = Math.max(dp[j],dp[j - stones[i]] + stones[i]);
}
}
return (sum - dp[target]) - dp[target];
}
// 494.目标和
// 容量为(sum + target) / 2的背包问题
// 与前两个题不一样的是,求方案个数
// 递归条件一般是dp[j] += dp[j - nums[i]]
public int findTargetSumWays(int[] nums, int target) {
int n = nums.length;
int sum = 0;
for (int i = 0; i < n; i ++)
{
sum += nums[i];
}
if ((sum + target) % 2 != 0) return 0;
if (Math.abs(target) > sum) return 0;
int t = (sum + target) / 2;
if (t < 0) t = -t;
int[] dp = new int[t + 1];
dp[0] = 1;
for (int i = 0; i < n; i ++)
{
for (int j = t; j >= nums[i]; j --)
{
dp[j] += dp[j - nums[i]];
}
}
return dp[t];
}
// 494.一和零
// 之前的题目背包容量是一维的,即target
// 该题目背包容量为二维的,即m与n,需要二维数组dp
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp = new int[m + 1][n + 1];
int oneNum, zeroNum;
// 遍历物品(strs数组)
for(String str:strs)
{
// oneNum和zeroNum为物品的重量
oneNum = 0;
zeroNum = 0;
for (int i = 0; i < str.length(); i ++)
{
if (str.charAt(i) == '0') zeroNum ++;
else oneNum ++;
}
// 遍历背包容量
for(int i = m; i >= zeroNum; i --)
{
for (int j = n; j >= oneNum; j --)
{
dp[i][j] = Math.max(dp[i][j], dp[i-zeroNum][j-oneNum] + 1);
}
}
}
return dp[m][n];
}
}
完全背包
你有一个背包,最多能容纳的体积是V,现在有n个物品,每种物品有任意多个,第i个物品的体积为 v i v_i vi ,价值为 w i w_i wi,则
(1) 求这个背包至多能装多大价值的物品?
(2) 若背包恰好装满,求至多能装多大价值的物品?
牛客网:
【模板】完全背包
代码和01背包的区别为在于遍历背包容量:
1.01背包倒序遍历
for (int j = V; j >= weight[i]; j --)
{
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
2.完全背包顺序遍历
for (int j = weight[i]; j <= V; j ++)
{
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
Java代码:
import java.util.Scanner;
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
while (in.hasNextInt()) { // 注意 while 处理多个 case
int n = in.nextInt();
int V = in.nextInt();
int[] weight = new int[n];
int[] value = new int[n];
int[] dp = new int[V+1];
for (int i = 0; i < n; i ++)
{
weight[i] = in.nextInt();
value[i] = in.nextInt();
}
for (int i = 0; i < n; i ++)
{
// 这个地方与01背包不同
for (int j = weight[i]; j <= V; j ++)
{
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
System.out.println(dp[V]);
int[] dp1 = new int[V+1];
Arrays.fill(dp1,Integer.MIN_VALUE);
dp1[0] = 0;
for (int i = 0; i < n; i ++)
{
for (int j = weight[i]; j <= V; j ++)
{
dp1[j] = Math.max(dp1[j], dp1[j - weight[i]] + value[i]);
if (dp1[j] < 0)
{
dp1[j] = Integer.MIN_VALUE;
}
}
}
System.out.println(dp1[V] == Integer.MIN_VALUE ? 0: dp1[V]);
}
}
}
力扣相关题目:
518.零钱兑换II
322.零钱兑换
279.完全平方数
377. 组合总和 Ⅳ
139.单词拆分
Java代码:
class Solution {
// 518.零钱兑换II
// 求组合数问题:dp[j] += dp[j - coins[i]];
public int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int i = 0; i < coins.length; i ++)
{
for (int j = coins[i]; j <= amount; j ++)
{
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
// 322.零钱兑换
// 这次不是求最大值,而是求最小值
// 在初始化是需要Integer.MAX_VALUE填充
public int coinChange(int[] coins, int amount) {
int maxn = Integer.MAX_VALUE;
int n = coins.length;
int[] dp = new int[amount + 1];
Arrays.fill(dp,maxn);
dp[0] = 0;
for (int i = 0; i < n; i ++)
{
for (int j = coins[i]; j <= amount; j ++)
{
if (dp[j - coins[i]] != maxn) dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
}
}
return dp[amount] == maxn ? -1:dp[amount];
}
// 279.完全平方数
public int numSquares(int n) {
int maxn = Integer.MAX_VALUE;
int[] dp = new int[n + 1];
Arrays.fill(dp,maxn);
dp[0] = 0;
for (int i = 1; i*i <= n; i ++)
{
for (int j = i*i; j <= n; j ++)
{
if (dp[j - i*i] != maxn) dp[j] = Math.min(dp[j], dp[j - i*i] + 1);
}
}
return dp[n] == maxn ? -1 : dp[n];
}
// 377. 组合总和 Ⅳ
// 求排列(有顺序的),不是组合
// 先遍历容量
public int combinationSum4(int[] nums, int target) {
int n = nums.length;
int[] dp = new int[target + 1];
dp[0] = 1;
for (int i = 0; i <= target; i ++)
{
for (int j = 0; j < n; j ++)
{
if (i >= nums[j]) dp[i] += dp[i - nums[j]];
}
}
return dp[target];
}
// 139.单词拆分
public boolean wordBreak(String s, List<String> wordDict) {
int n = s.length();
boolean[] dp = new boolean[n + 1];
dp[0] = true;
for (int i = 1; i <= n; i ++)
{
for (String word:wordDict)
{
int len = word.length();
if (i >= len && dp[i - len] && word.equals(s.substring(i - len,i))) dp[i] = true;
}
}
return dp[n];
}
}
背包总结:
递推公式:
- 问能否能装满背包(或者最多装多少):dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
- 问装满背包有几种方法:dp[j] += dp[j - nums[i]]
- 问背包装满最大价值:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
- 问装满背包所有物品的最小个数:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
组合or排列
- 组合外循环物品,内循环容量
- 排列外循环容量,内循环物品
打家劫舍
力扣相关题目:
198.打家劫舍
213.打家劫舍II
337.打家劫舍 III
Java代码:
class Solution {
// 198.打家劫舍
// 一个数组相邻之间不能连着偷,如何偷才能得到最大金钱
public int rob(int[] nums) {
if (nums == null || nums.length == 0) return 0;
if (nums.length == 1) return nums[0];
int n = nums.length;
int[] dp = new int[n];
dp[0] = nums[0];
dp[1] = Math.max(nums[0],nums[1]);
for (int i = 2; i < n; i ++)
{
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[n - 1];
}
// 213.打家劫舍II
// 数组成环了,然后相邻的不能连着偷
// 情况一:考虑包含首元素,不包含尾元素
// 情况二:考虑包含尾元素,不包含首元素
// 两种情况求最大
public int robRange(int[] nums, int start, int end)
{
if (start == end) return nums[start];
int[] dp = new int[nums.length];
dp[start] = nums[start];
dp[start + 1] = Math.max(nums[start], nums[start + 1]);
for (int i = start + 2; i <= end; i++)
{
dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[end];
}
// 337.打家劫舍 III
// 采用后序遍历
// 如果是偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0];
// 如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);
// 最后当前节点的状态就是{val2, val1}; 即:{不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}
public int rob(int[] nums) {
if (nums == null || nums.length == 0) return 0;
if (nums.length == 1) return nums[0];
int n = nums.length;
int result1 = robRange(nums,0,n - 2);
int result2 = robRange(nums,1,n - 1);
return Math.max(result1,result2);
}
public int[] robTree(TreeNode root)
{
if (root == null) return new int[]{0,0};
int[] left = robTree(root.left);
int[] right = robTree(root.right);
int var1 = root.val + left[0] + right[0];
int var2 = Math.max(left[0],left[1]) + Math.max(right[0],right[1]);
return new int[] {var2,var1};
}
public int rob(TreeNode root) {
int[] result = robTree(root);
return Math.max(result[0],result[1]);
}
}
买卖股票的最佳时机(状态转换)
力扣相关题目:
121. 买卖股票的最佳时机
122.买卖股票的最佳时机II
123.买卖股票的最佳时机III
188.买卖股票的最佳时机IV
309.最佳买卖股票时机含冷冻期
714.买卖股票的最佳时机含手续费
Java代码:
class Solution {
// 121. 买卖股票的最佳时机
// dp[i][0]表示第i天持有股票所得最多现金
// dp[i][1]表示第i天不持有股票所得最多现金
// 如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来:
// 1.第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
// 2.第i天买入股票,所得现金就是买入今天的股票后所得现金即:-prices[i]
// dp[i][0] = max(dp[i - 1][0], -prices[i]);
// 如果第i天不持有股票即dp[i][1], 也可以由两个状态推出来
// 1.第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
// 2.第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金即:prices[i] + dp[i - 1][0]
// dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
public int maxProfit(int[] prices) {
int n = prices.length;
if (n == 0) return 0;
int[][] dp = new int[n][2];
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < n; i ++)
{
dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
}
return dp[n - 1][1];
}
// 122.买卖股票的最佳时机II
public int maxProfit(int[] prices) {
int n = prices.length;
if (n == 0) return 0;
int[][] dp = new int[n][2];
dp[0][0] -= prices[0];
dp[0][1] = 0;
for (int i = 1; i < n; i ++)
{
// 与上题的区别只在这里
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
}
return dp[n - 1][1];
}
// 123.买卖股票的最佳时机III
// dp有五个状态
// 0.没有操作 (其实我们也可以不设置这个状态)
// 1.第一次持有股票
// 2.第一次不持有股票
// 3.第二次持有股票
// 4.第二次不持有股票
public int maxProfit(int[] prices) {
int n = prices.length;
if (n == 0) return 0;
int[][] dp = new int[n][5];
dp[0][1] -= prices[0];
dp[0][3] -= prices[0];
for (int i = 1; i < n; i ++)
{
dp[i][0] = dp[i - 1][0];
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
}
return dp[n - 1][4];
}
// 188.买卖股票的最佳时机IV
// 上一题拓展到通用的k个股票
public int maxProfit(int k, int[] prices) {
int n = prices.length;
if (n == 0) return 0;
int[][] dp = new int[n][2*k + 1];
for (int i = 1; i < 2*k; i += 2)
{
dp[0][i] -= prices[0];
}
for (int i = 1; i < n; i ++)
{
for (int j = 0; j < 2*k - 1; j += 2 )
{
dp[i][j + 1] = Math.max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
dp[i][j + 2] = Math.max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
}
}
return dp[n - 1][2*k];
}
// 309.最佳买卖股票时机含冷冻期
// 需要结合状态转换图来理解
public int maxProfit(int[] prices) {
int n = prices.length;
if (n == 0) return 0;
int[][] dp = new int[n][4];
dp[0][0] -= prices[0];
for (int i = 1; i < n; i ++)
{
dp[i][0] = Math.max(dp[i - 1][0], Math.max(dp[i - 1][1] - prices[i], dp[i - 1][3] - prices[i]));
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][3]);
dp[i][2] = dp[i - 1][0] + prices[i];
dp[i][3] = dp[i - 1][2];
}
return Math.max(dp[n - 1][1], Math.max(dp[n - 1][2], dp[n - 1][3]));
}
// 714.买卖股票的最佳时机含手续费
//与122类似,就是多了手续费
public int maxProfit(int[] prices, int fee) {
int n = prices.length;
if (n == 0) return 0;
int[][] dp = new int[n][2];
dp[0][0] -= prices[0];
for (int i = 1; i < n; i ++)
{
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
// 卖出需要手续费
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
}
return Math.max(dp[n - 1][0], dp[n - 1][1]);
}
}
最长子序列
力扣相关题目:
300.最长递增子序列
674. 最长连续递增序列
53. 最大子序和
718. 最长重复子数组
Java代码:
class Solution {
// 300.最长递增子序列
// dp[i]: 表示i之前包括i的以nums[i]结尾的最长递增子序列的长度
// 位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值
// if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1)
// 最后求全局的最大值
public int lengthOfLIS(int[] nums) {
int n = nums.length;
if (n <= 1) return n;
int[] dp = new int[n];
Arrays.fill(dp,1);
for (int i = 1; i < n; i ++)
{
for (int j = 0; j < i; j ++)
{
if (nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
int res = 0;
for (int i = 0; i < n; i ++)
{
res = Math.max(res, dp[i]);
}
return res;
}
// 674. 最长连续递增序列
// dp[i]:以下标i为结尾的连续递增的子序列长度
// 以 i 为结尾的连续递增的子序列长度 一定等于 以i - 1为结尾的连续递增的子序列长度 + 1
// dp[i] = dp[i - 1] + 1 与上题的区别
public int findLengthOfLCIS(int[] nums) {
int n = nums.length;
if (n == 0) return 0;
int res = 1;
int[] dp = new int[n];
Arrays.fill(dp,1);
for (int i = 1; i < n; i ++)
{
if (nums[i] > nums[i - 1]) dp[i] = dp[i - 1] + 1;
if (res < dp[i]) res = dp[i];
}
return res;
}
// 53. 最大子序和
// dp[i]两个方面
// 1.dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和
// 2.nums[i],即:从头开始计算当前连续子序列和
// 取最大值
public int maxSubArray(int[] nums) {
int n = nums.length;
if (n == 0) return 0;
int[] dp = new int[n];
dp[0] = nums[0];
int result = dp[0];
for (int i = 1; i < n; i ++)
{
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
if (dp[i] > result) result = dp[i];
}
return result;
}
// 718. 最长重复子数组
// dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]
// 与674很像
public int findLength(int[] nums1, int[] nums2) {
int n = nums1.length, m = nums2.length;
int[][] dp = new int[n][m];
for (int i = 0; i < n; i ++)
{
if (nums1[i] == nums2[0]) dp[i][0] = 1;
}
for (int j = 0; j < m; j ++)
{
if (nums2[j] == nums1[0]) dp[0][j] = 1;
}
int res = 0;
for (int i = 0; i < n; i ++)
{
for (int j = 0; j < m; j ++)
{
if (nums1[i] == nums2[j] && i > 0 && j > 0)
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
if (dp[i][j] > res) res = dp[i][j];
}
}
return res;
}
}
最长公共序列
力扣相关题目:
1143.最长公共子序列
1035.不相交的线
Java代码:
class Solution {
// 1143.最长公共子序列
// dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
// 两种情况:
// 1. 如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以dp[i][j] = dp[i - 1][j - 1] + 1;
// 2.如果text1[i - 1] 与 text2[j - 1]不相同,那就看看text1[0, i - 2]与text2[0, j - 1]的最长公共子序列 和 text1[0, i - 1]与text2[0, j - 2]的最长公共子序列,取最大的
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m+1][n+1];
for (int i = 1; i <= m; i ++)
{
for (int j = 1; j <= n; j ++)
{
if (text1.charAt(i - 1) == text2.charAt(j - 1))
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else
{
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
// 1035.不相交的线
// 与上一题等价
public int maxUncrossedLines(int[] nums1, int[] nums2) {
int n = nums1.length, m = nums2.length;
int[][] dp = new int[n + 1][m + 1];
for (int i = 1; i <= n; i ++)
{
for (int j = 1; j <= m; j ++)
{
if (nums1[i - 1] == nums2[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else
{
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[n][m];
}
}
子序列有关(删除元素)
力扣相关题目:
392.判断子序列
115.不同的子序列
583. 两个字符串的删除操作
72. 编辑距离
Java代码:
class Solution {
// 392.判断子序列
// dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度
// s[i - 1] == t[j - 1]:t中找到了一个字符在s中也出现了
// s[i - 1] != t[j - 1]:相当于t要删除元素,继续匹配
public boolean isSubsequence(String s, String t) {
int n = s.length(), m = t.length();
int[][] dp = new int[n + 1][m + 1];
for (int i = 1; i <= n; i ++)
{
for (int j = 1; j <= m; j ++)
{
if (s.charAt(i - 1) == t.charAt(j - 1))
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}else
{
dp[i][j] = dp[i][j - 1];
}
}
}
if (dp[n][m] == n) return true;
return false;
}
// 115.不同的子序列
// dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]
// s[i - 1] == t[j - 1]:
// 1.一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。即不需要考虑当前s子串和t子串的最后一位字母,所以只需要 dp[i-1][j-1]
// 2.一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]
// s[i - 1] != t[j - 1]:不用s[i - 1]来匹配(就是模拟在s中删除这个元素)
public int numDistinct(String s, String t) {
int n = s.length(), m = t.length();
int[][] dp = new int[n + 1][m + 1];
for (int i = 0; i <= n; i ++) dp[i][0] = 1;
for (int j = 1; j <= m; j ++) dp[0][j] = 0;
for (int i = 1; i <= n; i ++)
{
for (int j = 1; j <= m; j ++)
{
if (s.charAt(i - 1) == t.charAt(j - 1))
{
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
}
else dp[i][j] = dp[i - 1][j];
}
}
return dp[n][m];
}
// 583. 两个字符串的删除操作
// dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2
// s[i - 1] == t[j - 1]:dp[i][j] = dp[i - 1][j - 1];
// s[i - 1] != t[j - 1]:删除s的或t的
public int minDistance(String word1, String word2) {
int n = word1.length(), m = word2.length();
int[][] dp = new int[n + 1][m + 1];
for (int i = 0; i <= n; i ++) dp[i][0] = i;
for (int j = 0; j <= m; j ++) dp[0][j] = j;
for (int i = 1; i <= n; i ++)
{
for (int j = 1; j <= m; j ++)
{
if (word1.charAt(i - 1) == word2.charAt(j - 1))
{
dp[i][j] = dp[i - 1][j - 1];
}
else
{
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
}
}
}
return dp[n][m];
}
// 72. 编辑距离
// 删除或添加可以都看作是删除
// word1替换word1[i - 1],使其与word2[j - 1]相同
public int minDistance(String word1, String word2) {
int n = word1.length(), m = word2.length();
int[][] dp = new int[n + 1][m + 1];
for (int i = 0; i <= n; i ++) dp[i][0] = i;
for (int j = 1; j <= m; j ++) dp[0][j] = j;
for (int i = 1; i <= n; i ++)
{
for (int j = 1; j <= m; j ++)
{
if (word1.charAt(i - 1) == word2.charAt(j - 1))
{
dp[i][j] = dp[i - 1][j - 1];
}
else
{
dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1]))+1;
}
}
}
return dp[n][m];
}
}
回文子串
力扣相关题目:
647. 回文子串
516.最长回文子序列
Java代码:
class Solution {
// 647. 回文子串
// 只考虑s[i] == t[j]的情况
// 1.下标i 与 j相同,同一个字符例如a,当然是回文子串
// 2.下标i 与 j相差为1,例如aa,也是回文子串
// 3.下标:i 与 j相差大于1的时候,例如cabac,此时s[i]与s[j]已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是 i+1 与 j-1区间,这个区间是不是回文就看dp[i + 1][j - 1]是否为true。
public int countSubstrings(String s) {
int n = s.length();
boolean[][] dp = new boolean[n][n];
int res = 0;
for (int i = 0; i < n; i ++)
{
for (int j = 0; j < n; j ++)
{
dp[i][j] = false;
}
}
for (int i = n - 1; i >= 0; i --)
{
for (int j = i; j < n; j ++)
{
if (s.charAt(i) == s.charAt(j))
{
if (j - i <= 1)
{
res ++;
dp[i][j] = true;
}else if (dp[i + 1][j - 1])
{
res ++;
dp[i][j] = true;
}
}
}
}
return res;
}
// 516.最长回文子序列
// 如果s[i]与s[j]不相同,说明s[i]和s[j]的同时加入 并不能增加[i,j]区间回文子序列的长度,那么分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列
public int longestPalindromeSubseq(String s) {
int n = s.length();
int[][] dp = new int[n][n];
for (int i = 0; i < n; i ++) dp[i][i] = 1;
for (int i = n - 1; i >= 0; i --)
{
for (int j = i + 1; j < n; j ++)
{
if (s.charAt(i) == s.charAt(j))
{
dp[i][j] = dp[i + 1][j - 1] + 2;
}else{
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
}
}
}
return dp[0][n - 1];
}
}