目录
898. 数字三角形 + 输出最优路径
题目:
给定如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
7 3 8 8 1 0 2 7 4 4 4 5 2 6 5
思路:
从底向上枚举,这样不用判断边界问题
在每个小三角形分支中,顶上的累加其下端两分支的最大值,则最优解就是顶部f[1][1]
import java.util.*;
class Main
{
static int N=510;
static int[][] f=new int[N][N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
for(int i=1;i<=n;i++) //枚举层数
for(int j=1;j<=i;j++) f[i][j]=sc.nextInt();
for(int i=n-1;i>=1;i--) //从底向上
for(int j=1;j<=i;j++)
f[i][j]+=Math.max(f[i+1][j],f[i+1][j+1]);
System.out.print(f[1][1]);
}
}
输出最优路径:
用map存两个坐标间的指向
import java.util.*;
class Main
{
static int N=510;
static int[][] f=new int[N][N],a=new int[N][N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
Map<List,List> mp=new HashMap<>();
for(int i=1;i<=n;i++) //枚举层数
for(int j=1;j<=i;j++) a[i][j]=f[i][j]=sc.nextInt();
for(int i=n-1;i>=1;i--) //从底向上
for(int j=1;j<=i;j++)
{
int t=Math.max(f[i+1][j],f[i+1][j+1]);
List<Integer> top=new LinkedList<>();
List<Integer> tt=new LinkedList<>();
top.add(i); top.add(j);
if(t==f[i+1][j])
{
tt.add(i+1); tt.add(j);
mp.put(top,tt);
}else
{
tt.add(i+1); tt.add(j+1);
mp.put(top,tt);
}
f[i][j]+=t;
}
int nx=1,ny=1;
List<Integer> res=new LinkedList<>();
while(nx!=n&&ny!=n)
{
res.add(a[nx][ny]);
List<Integer> list=new LinkedList<>();
list.add(nx); list.add(ny);
List<Integer> t=mp.get(list);
nx=t.get(0); ny=t.get(1);
}
res.add(a[nx][ny]);
System.out.println(f[1][1]);
System.out.print(res);
}
}
895. 最长上升子序列
题目:
给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
思路:
- 定义f[i]为以下标i结尾的最长上升子序列长度
- 遍历每个i前面的点j,如果满足a[j]<a[i],则f[i]=f[j]+1
- 最后res取所有f[i]中的最大值
import java.util.*;
class Main
{
static int N=1010;
static int[] f=new int[N],a=new int[N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
//定义f[i]为以下标i结尾的最大上升子序列值
for(int i=1;i<=n;i++) a[i]=sc.nextInt();
for(int i=1;i<=n;i++)
{
f[i]=1; //只有a[i]一个
for(int j=1;j<i;j++)
if(a[j]<a[i])
f[i]=Math.max(f[i],f[j]+1);
}
int res=0;
for(int i=1;i<=n;i++) res=Math.max(res,f[i]);
System.out.print(res);
}
}
897. 最长公共子序列
题目:
给定两个长度分别为 N 和 M 的字符串 A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少
思路:
定义f[i][j]为a的前i个字母和b的前j个字母的最长公共子序列长度
集合划分依据:以a[i],b[j]是否包含在子序列中为依据,可划分成4部分:
- a[i]不在,b[j]不在 —— f[i-1][j-1]
- a[i]在,b[j]不在
- 不能完全由f[i][j-1]表示,因为f[i][j-1]表示的是a的前i个字母和b的前j-1个字母中最长公共子序列的长度,但b[j]可能存在也可能不存在
- 但仍然可以用f[i][j-1]表示,因为"a[i]在,b[j]不在"的情况包含在f[i][j-1]中,而求max,对最终结果不影响
- 例如:要求a,b,c的最大值可以这样求:max(max(a,b),max(b,c)),虽然b被重复使用,但仍能求出max,求max只要保证不漏即可
- a[i]不在,b[j]在 —— 原理同上,f[i-1][j]
- a[i]在,b[j]在 —— f[i-1][j-1]+1
实际上,第一种情况包含在第二三情况中
import java.util.*;
class Main
{
static int N=1010;
static int[][] f=new int[N][N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
int n=sc.nextInt(),m=sc.nextInt();
String a=" "+sc.next();
String b=" "+sc.next();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
f[i][j]=Math.max(f[i-1][j],f[i][j-1]);
if(a.charAt(i)==b.charAt(j)) f[i][j]=f[i-1][j-1]+1;
}
System.out.print(f[n][m]);
}
}
1051. 最大的和 - 前后缀分解dp
题目:
对于给定的整数序列,找出两个不重合连续子段,使得两子段中所有数字的和最大,输出最大值
1 10 1 -1 2 2 3 -3 4 -4 5 -5
在样例中,我们取{2,2,3,-3,4}和{5}两个子段,即可得到答案
思路:
定义f[i]为【1~i】区间内连续和的最大值
定义g[i]为【i~n】区间内连续和的最大值
以f[i]为例:以是否包含a[i]划分集合
- 不包含a[i]:f[i-1]
- 包含a[i]:以a[i-1]结尾的最大连续区间和 + a[i]
对前缀f[i]和后缀g[i]进行处理
最后枚举1~n每个间断点,求最大值res即可
import java.util.*;
class Main
{
static int N=50010;
static int[] f=new int[N],g=new int[N];
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
int t=sc.nextInt();
while(t-->0)
{
int n=sc.nextInt();
int[] a=new int[n+1];
for(int i=1;i<=n;i++) a[i]=sc.nextInt();
Arrays.fill(f,-0x3f3f3f3f);
Arrays.fill(g,-0x3f3f3f3f);
int s=0;
for(int i=1;i<=n;i++)
{
s=Math.max(0,s)+a[i]; //s维护以a[i-1]结尾的最大连续和 这也是包含a[i]的情况
//s=0+a[i]时,相当于以a[i-1]结尾的最大连续和只包含a[i]
//s=s+a[i]时,相当于以a[i-1]结尾的最大连续和包含a[i]和之前的最大连续和
f[i]=Math.max(f[i-1],s); //max(不包含a[i],包含a[i])
}
s=0;
for(int i=n;i>=1;i--)
{
s=Math.max(0,s)+a[i];
g[i]=Math.max(g[i+1],s);
}
int res=-0x3f3f3f3f;
for(int i=1;i<=n;i++) res=Math.max(res,f[i]+g[i+1]);
System.out.println(res);
}
}
}