线性动规是动态规划的分支之一,其常见的问题有:最长上升子序列,合唱队形,挖地雷等。下文将对这些经典问题做出相应的解答分析。
最长上升子序列
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: 10,9,2,5,3,7,101,18
输出: 4
解释: 最长的上升子序列是 2,3,7,101,它的长度是 4。
分析解答:
由题意我们可知,“最长上升子序列”,顾名思义,序列的最后一个数一定是最大的。那我们不妨采用暴力破解的方式,依次让数组的每个值作为序列的最后一个数。在这里存放数据的数组我们用date表述,用数组B表示以date[i]结尾的最长子序列的长度。当我们要求以date[i]结尾的最长子序列时,必定要遍历下标 i 以前的每一个元素,不妨我们假设 下标i以前的数据date[j]的值小于date[i],那么以date[i]结尾的序列长度就等于以date[j]结尾的序列长度加1.要求最大序列长度,则要比较以上文方式求出来的序列长度,用状态转移方程可表示为 B[i] = max{B[j] + 1} = max{ B[j] } + 1
import java.util.Arrays;
import java.util.Scanner;
/*
最长上升子序列
*/
public class LongestAscendingSubsequence {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
//如果待测序列的长度为0
if(n == 0) {
System.out.println(0);
return;
}
int[] date = new int[n+1]; //1 - n用来存放星信息
int[] B = new int[n+1]; //用来记录以i结尾的最长上升子序列
for(int i=1;i<=n;i++) {
date[i] = sc.nextInt();
}
sc.close();
for(int i=1;i<=n;i++) {
int flag = 0;
//以第一个元素结尾的最长子序列为1
if(i == 1) {
B[i] = 1;
}
for(int j=1;j<i;j++) {
if(date[j]<date[i]) {
flag = 1;
B[i] = Math.max(B[i],B[j]+1);
}
}
if(flag == 0) {
B[i] = 1;
}
}
Arrays.parallelSort(B);
System.out.println(B[n]);
}
}
合唱队形
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK 则他们的身高满足T1<…Ti+1>…>TK(1≤i≤K)。你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式
输入的第一行是一个整数N,表示同学的总数。
第二行有n个整数,用空格分隔,第i个整数Ti是第i位同学的身高(厘米)。
输出格式
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
数据范围
2≤N≤100
130≤Ti≤230
输入样例:
8
186 186 150 200 160 130 197 220
输出样例:
4
分析解答:
分析题目,合唱队形实际上是中间高两边矮的一种队形。见下图
结合我们刚刚解决的"最长上升子序列"问题,"合唱队形"实际上是以date[i]结尾同时又以date[i]开头的最长子序列问题,我们只需分别找出这俩个子序列的最长长度,便可以求出最小的离队人数。
import java.util.Scanner;
/*
合唱队形
*/
public class ChorusFormation {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); //表示同学的个数
int[] date = new int[n];
for(int i=0;i<n;i++) {
date[i] = sc.nextInt();
}
sc.close();
//将date[]数组反向
int[] dateReverse = new int[n];
for(int i=n-1;i>=0;i--) {
dateReverse[n-(i+1)] = date[i];
}
int[] leftB = new int[n]; //以date[i]为尾的最长最长子序列
int[] rightB = new int[n]; //以date[i]为头的最长最长子序列
f(date,leftB); //从左往右遍历,得到以date[i]结尾的最长子序列
f(dateReverse,rightB); //从右往左遍历,得到以date[i]开头的最长子序列
int min=n-(leftB[0]+rightB[n-1]-1);
for(int i=0;i<n;i++) {
int temp = n-(leftB[i]+rightB[n-i-1]-1);
if(temp<min) {
min = temp;
}
}
System.out.println(min);
}
public static void f(int[] dateArray,int[] B) {
int n = dateArray.length;
for(int i=0;i<n;i++) {
if(i == 0) {
B[i] = 1;
}
int flag=0;
for(int j=0;j<i;j++) {
if(dateArray[j]<dateArray[i]) {
flag = 1;
B[i] = Math.max(B[i], B[j]+1);
}
}
if(flag == 0) {
B[i] = 1;
}
}
}
}
以上即是对线性动规问题的简单分析。