482. 合唱队形
N N N位同学站成一排,音乐老师要请其中的 ( N − K ) (N-K) (N−K)位同学出列,使得剩下的 K K K位同学排成合唱队形。
合唱队形是指这样的一种队形:设 K K K位同学从左到右依次编号为 1 , 2 … , K , 1,2…,K, 1,2…,K,他们的身高分别为 T 1 , T 2 , … , T K T_1,T_2,…,T_K T1,T2,…,TK, 则他们的身高满足 T 1 < … < T i > T i + 1 > … > T K ( 1 ≤ i ≤ K ) T_1<…<T_i>T_{i+1}>…>T_K(1≤i≤K) T1<…<Ti>Ti+1>…>TK(1≤i≤K)。
你的任务是,已知所有 N N N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式
输入的第一行是一个整数 N N N,表示同学的总数。
第二行有 n n n个整数,用空格分隔,第 i i i个整数 T i T_i Ti是第 i i i位同学的身高(厘米)。
输出格式
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
数据范围
2 ≤ N ≤ 100 2≤N≤100 2≤N≤100,
130 ≤ T i ≤ 230 130≤T_i≤230 130≤Ti≤230
输入样例:
8
186 186 150 200 160 130 197 220
输出样例:
4
思路:
T 1 < … < T i > T i + 1 > … > T K ( 1 ≤ i ≤ K ) T_1<…<T_i>T_{i+1}>…>T_K(1≤i≤K) T1<…<Ti>Ti+1>…>TK(1≤i≤K)表示了三种情况。
i = 1
, T i T_i Ti就是第一个数,故这是一个完全递减
序列。i = k
, T i T_i Ti就是最后一个数,故这是一个完全递增
序列。i
在1~k
之间,则 T i T_i Ti是位于序列中间的最大的数,左边
是递增
的序列,右边
是递减
的序列。
算法如下
(线性DP,最长上升子序列) O ( n 2 ) O(n^2) O(n2)
假设最优解的中心是第 i
个人,则 T 1 , T 2 , … , T i T_1,T_2,…,T_i T1,T2,…,Ti一定是以 T i T_i Ti结尾的最长上升子序列。
同理, T i , T i + 1 , . . . T K T_i,T_{i+1},...T_K Ti,Ti+1,...TK一定是以 T K T_K TK结尾的最长下降子序列,而从前往后
的最长下降子序列可以转换为求从后往前
的最长上升子序列。
因此可以先预处理出:
从前往后以每个点结尾的最长上升子序列长度 f[i]
;
从后往前以每个点结尾的最长上升子序列长度 g[i]
;
那么以 k
为中心的最大长度就是 f[k] + g[k] - 1
(因为在求以 T K T_K TK结尾的最长子序列时,f[k]
与g[k]
都包含了 T K T_K TK,故要减去一个),遍历 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);
}
}