1、数字三角形
1.题目描述
2.思路
分析方法:闫氏DP分析
时间复杂度:遍历二维O(n^2),求最大值O(1) => O(n^2)
3.代码
package chapter05.src;
import java.util.Scanner;
/**
* @author mys
* @date 2022/3/22 18:23
*/
public class p898 {
static int N = 510;
public static void main(String[] args) {
int[][] a = new int[N][N], f = new int[N][N];//a:数字三角形 f:状态
Scanner input = new Scanner(System.in);
int n = input.nextInt();//三角形的层数
//输入
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= i; j ++) {
a[i][j] = input.nextInt();
}
}
//从下往上开始遍历
for (int i = n; i >= 1; i --) {
for (int j = 1; j <= i; j ++) {
//f[i + 1][j + 1]:从右边上去的值 f[i + 1][j]:从左边上去的值
f[i][j] = Math.max(f[i + 1][j + 1] + a[i][j], f[i + 1][j] + a[i][j]);
}
}
//输出最顶端元素和
System.out.println(f[1][1]);
}
}
代码优化:
1、二维数组a可以不要,直接用f表示即可
2、代码写法的优化 :f[i][j] = Math.max(f[i + 1][j + 1] + a[i][j], f[i + 1][j] + a[i][j]);
=> f[i][j] += Math.max(f[i + 1][j + 1], f[i + 1][j]);
逻辑不变,写法简单
最终代码
import java.util.Scanner;
/**
* @author mys
* @date 2022/3/22 18:23
*/
public class Main {
static int N = 510;
public static void main(String[] args) {
int[][] f = new int[N][N];//a:数字三角形 f:状态
Scanner input = new Scanner(System.in);
int n = input.nextInt();//三角形的层数
//输入
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= i; j ++) {
f[i][j] = input.nextInt();
}
}
//从下往上开始遍历
for (int i = n; i >= 1; i --) {
for (int j = 1; j <= i; j ++) {
//f[i + 1][j + 1]:从右边上去的值 f[i + 1][j]:从左边上去的值
f[i][j] += Math.max(f[i + 1][j + 1], f[i + 1][j]);
}
}
//输出最顶端元素和
System.out.println(f[1][1]);
}
}
2、最长上升子序列
1.题目描述
2.思路
f[i] : 从第一个数字开始计算,以a[i]结尾的最长上升子序列
时间复杂度:状态数n 每个状态需要的时间 n -->O(n^2)
3.代码
package chapter05;
import java.util.Scanner;
/**
* @author mys
* @date 2022/3/24 11:24
*/
public class p895 {
static int N = 1010;
public static void main(String[] args) {
int[] a = new int[N], f = new int[N];//a:序列 f:状态
Scanner input = new Scanner(System.in);
int n = input.nextInt();//序列长度
//输入序列
for (int i = 0; i < n; i ++) {
a[i] = input.nextInt();
}
for (int i = 0; i < n; i ++) {//从前往后计算某个状态的上升序列长度
f[i] = 1;//只有a[i]一个数时,上升序列最长为1
for (int j = 0; j < i; j ++) {
if (a[j] < a[i]) {//f[j]:i之前的小于自己值的最长子序列长度
f[i] = Math.max(f[i], f[j] + 1);//f[j] + 1:+1表示加上自己
}
}
}
//输出最长子序列
int res = 0;
for (int i = 0; i < n; i ++) {
res = Math.max(res, f[i]);
}
System.out.println(res);
}
}
4.优化
如果数据范围增大到如下,用上面的O(n^2)复杂度的就会超时
数据范围:1≤N≤100000,−109≤数列中的数≤109
思路:模拟一个堆栈(用数组表示栈比直接使用库函数更快),遍历元素,如果该元素大于栈顶元素,入栈;否则替换掉第一个大于或等于该元素的数字
例 n: 7
arr : 3 1 2 1 8 5 6
stk : 3
1 比 3 小
stk : 1
2 比 1 大
stk : 1 2
1 比 2 小
stk : 1 2
8 比 2 大
stk : 1 2 8
5 比 8 小
stk : 1 2 5
6 比 5 大
stk : 1 2 5 6
stk 的长度就是最长递增子序列的长度
最终代码实现:
import java.util.Scanner;
/**
* @author mys
* @date 2022/3/24 12:26
* 数据范围增大,O(n^2)会超时,使用优化方法,时间复杂度O(nlogn)
*/
public class p896 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
//输入序列
int[] a = new int[n];
for (int i = 0; i < n; i ++) {
a[i] = input.nextInt();
}
int[] stk = new int[n];//模拟堆栈
stk[0] = a[0];//现将第一个元素入栈
int tt = 0;
for (int i = 1; i < n; i ++) {
if (a[i] > stk[tt]) {//如果当前元素大于栈顶元素,当前元素入栈
stk[++ tt] = a[i];
} else {
//否则找到栈中第一个大于or等于该元素的数字,并用该元素替换
//因为栈中元素是有序递增的,可以使用二分查找
int l = 0, r = tt;
while (l < r) {
int mid = (l + r) >> 1;
if (stk[mid] >= a[i]) {//数字在左半部分
r = mid;
} else {//数字在右半部分
l = mid + 1;
}
}
stk[l] = a[i];//l就是查找到的位置,替换
}
}
System.out.println(tt + 1);
}
}
3、最长公共子序列
1.题目描述
2.思路
3.代码
/**
* @author mys
* @date 2022/3/24 13:28
*/
public class poffer95_longestCommonSubsequence {
public int longestCommonSubsequence(String text1, String text2) {
int n = text1.length(), m = text2.length();
int[][] f = new int[n + 1][m + 1];//状态
//遍历所有状态
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++) {
//分为a[i] == a[j]和不等于两种情况来看
if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
f[i][j] = f[i - 1][j - 1] + 1;//等于
} else {
f[i][j] = Math.max(f[i - 1][j], f[i][j - 1]);//不等于,再看是否包含a[i]或者b[j]
}
}
}
return f[n][m];
}
}
对应leetcode题目:剑指 Offer II 095. 最长公共子序列
4、最短编辑距离
1.题目描述
2.思路
集合划分的思路:通常看最后一个操作之前的状态,然后加入最后一个操作的状态
时间复杂度:遍历所有状态:n^2,每个状态操作次数:3,==> O(n^2)
3.代码
package chapter05;
import java.util.Scanner;
/**
* @author mys
* @date 2022/3/25 13:38
*/
public class p902 {
static int N = 1010;
public static void main(String[] args) {
//输入
Scanner input = new Scanner(System.in);
int n = input.nextInt();
String a = input.next();
int m = input.nextInt();
String b = input.next();
int[][] f = new int[N][N];
//边界初始化
//1.从a的前0个字符匹配b的前i个字符,进行的是插入操作,操作次数就是i
for (int i = 0; i <= m; i ++) {
f[0][i] = i;
}
//2.从a的前i个字符匹配b的前0个字符,进行的是删除操作,操作次数就是i
for (int i = 0; i <= n; i ++) {
f[i][0] = i;
}
//遍历所有状态
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++) {
//(1)f[i - 1][j] + 1:删除 (2)f[i][j - 1] + 1:插入
f[i][j] = Math.min(f[i - 1][j] + 1, f[i][j - 1] + 1);
//(3)替换,分为两种情况 charAt(i - 1):从1开始,所以需要-1
if (a.charAt(i - 1) == b.charAt(j - 1)) {
f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]);
} else {
f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + 1);
}
}
}
//求得把a的前n个字符与b的前m个字符进行匹配
System.out.println(f[n][m]);
}
}
4.改变条件
代码:
package chapter05.src;
import java.util.Scanner;
/**
* @author mys
* @date 2022/3/25 14:15
*/
public class p899 {
static int N = 1010, M = 15;
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();//字符串个数
int m = input.nextInt();//询问次数
String[] str = new String[N];
for (int i = 0; i < n; i ++) {
str[i] = input.next();
}
for (int i = 0; i < m; i ++) {
int count = 0;
String s = input.next();
int limit = input.nextInt();//限制的操作次数
for (int j = 0; j < n; j ++) {
if (minDistance(str[j], s) <= limit) {
count++;
}
}
System.out.println(count);
}
}
public static int minDistance(String s1, String s2) {
int n = s1.length(), m = s2.length();
int[][] f = new int[M][M];
//边界条件初始化
//1.从a的前0个字符匹配b的前i个字符,进行的是插入操作,操作次数就是i
for (int i = 0; i <= m; i ++) {
f[0][i] = i;
}
//2.从a的前i个字符匹配b的前0个字符,进行的是删除操作,操作次数就是i
for (int i = 0; i <= n; i ++) {
f[i][0] = i;
}
//遍历所有状态
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++) {
//f[i - 1][j] + 1:删除 f[i][j - 1] + 1:插入
f[i][j] = Math.min(f[i - 1][j] + 1, f[i][j - 1] + 1);
//替换,分为两种情况
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]);
} else {
f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + 1);
}
}
}
//求得把a的前n个字符与b的前m个字符进行匹配的操作次数
return f[n][m];
}
}