482. 合唱队形
$N$位同学站成一排,音乐老师要请其中的$(N-K)$位同学出列,使得剩下的$K$位同学排成合唱队形。
合唱队形是指这样的一种队形:设$K$位同学从左到右依次编号为$1,2…,K,$他们的身高分别为$T_1,T_2,…,T_K$, 则他们的身高满足$T_1<…<T_i>T_{i+1}>…>T_K(1≤i≤K)$。
你的任务是,已知所有$N$位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式
输入的第一行是一个整数$N$,表示同学的总数。
第二行有$n$个整数,用空格分隔,第$i$个整数$T_i$是第$i$位同学的身高(厘米)。
输出格式
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
数据范围
$2≤N≤100$,
$130≤T_i≤230$
输入样例:
8
186 186 150 200 160 130 197 220
输出样例:
4
思路:
$T_1<…<T_i>T_{i+1}>…>T_K(1≤i≤K)$表示了三种情况。
1. i = 1
,$T_i$就是第一个数,故这是一个完全递减
序列。
2. i = k
,$T_i$就是最后一个数,故这是一个完全递增
序列。
3. i
在1~k
之间,则$T_i$是位于序列中间的最大的数,左边
是递增
的序列,右边
是递减
的序列。
算法如下
(线性DP,最长上升子序列) $O(n^2)$
假设最优解的中心是第 i
个人,则 $T_1,T_2,…,T_i$一定是以 $T_i$结尾的最长上升子序列。
同理,$T_i,T_{i+1},…T_K$一定是以 $T_K$结尾的最长下降子序列,而从前往后
的最长下降子序列可以转换为求从后往前
的最长上升子序列。
因此可以先预处理出:
从前往后以每个点结尾的最长上升子序列长度 f[i]
;
从后往前以每个点结尾的最长上升子序列长度 g[i]
;
那么以 k
为中心的最大长度就是 f[k] + g[k] - 1
(因为在求以$T_K$结尾的最长子序列时,都加了一次$T_K$,故要减去一次),遍历 k = 1, 2, ..., n
取最大值即为答案。
求最长上升子序列的算法:
动态规划:
1、确定状态,对于最优的策略,一定有最后一个元素nums[i]
,最长上升子序列最短是1
,就是当前元素本身,当子序列长度大于1
的时候,那么最终最优策略里面的nums[i]
的前一个元素是nums[j]
,即剔除了j~i
之间的其他元素,因为是最优策略,所以他选中的以nums[j]
结尾的上升子序列一定是0~j
中最长的子序列。
2、转移方程: dp[i]
表示以nums[i]
结尾的最长上升子序列的长度,当i>j&&nums[i] > nums[j]
时,dp[i] = max{dp[i],dp[j]+1}
3、初始状态:每个dp[i]
开始至少是1
4、计算顺序,下标由小到大,我们需要的dp[k]
即以第k
个元素结尾的最长上升子序列。
LeetCode 300 .最长递增子序列 Java代码
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums==null || nums.length==0) return 0;
int[] dp = new int[nums.length];
int res = 0;//保存本轮外层for循环产生的最大的上升子序列长度
for(int i = 0;i<nums.length;i++){
dp[i] = 1;//初始至少为1
for(int j = 0;j<i;j++){//计算j之前的所有最长的,看其+1后是否大于dp[j]
if(nums[i] > nums[j] ){
dp[i] = Math.max(dp[i],dp[j] + 1);
}
}
res = Math.max(res,dp[i]);
}
return res;
}
}
本题Java代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] nums = new int[n];
int[] f = new int[n];
int[] g = new int[n];
for(int i = 0;i < n;i++){
nums[i] = scanner.nextInt();
}
//求最长上升子序列的动态数组f[n]
for(int i = 0;i < n;i++){
f[i] = 1;
for(int j = 0;j < i;j++){
if(nums[i] > nums[j]){
f[i] = Math.max(f[i],f[j] + 1);
}
}
}
//求最长下降子序列的动态数组g[n],反着求最长上升子序列即可
for(int i = n - 1;i >= 0;i--){
g[i] = 1;
for(int j = n -1;j > i;j--){
if(nums[i] > nums[j]){
g[i] = Math.max(g[i],g[j] + 1);
}
}
}
//求出以每一个Tk作为最高点时,能留下多少同学,取最大值
int res = Integer.MIN_VALUE;
for(int k = 0;k < n;k++){
res = Math.max(res,f[k] + g[k] - 1);
}
//输出应该出列多少同学
System.out.println(n - res);
}
}