合唱队形 - 最长升序子序列的个数如何统计

567 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

一、题目描述:

n 位同学站成一排,音乐老师要请其中的 n-k 位同学出列,使得剩下的 k 位同学排成合唱队形。

合唱队形是指这样的一种队形:设 k 位同学从左到右依次编号为 1,2, … ,k,他们的身高分别为 t1,t2, … ,tk,则他们的身高满足 t1 < ⋯ < ti > ti+1 > … >tk (1≤i≤k)。

你的任务是,已知所有 n 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

来源:洛谷 www.luogu.com.cn/problem/P10…

输入格式

共二行。

第一行是一个整数 n(2≤n≤100),表示同学的总数。

第二行有 n 个整数,用空格分隔,第 i 个整数 ti(130≤ti≤230)是第 i 位同学的身高(厘米)。

8
186 186 150 200 160 130 197 220

输出格式

一个整数,最少需要几位同学出列。

4

二、思路分析:

这道题目真的很巧妙(在我看来🤡),首先我们看懂题目,同学们的位置是不变的,只能有人退出去形成合唱队列,这个合唱队列是指以第 i 位同学开始向左看,身高是逐渐变矮的, 以第 i 位同学开始向右看,身高也是逐渐变矮的, 注意这个i位置可以是任何位置。 与我们生活中的合唱队列不一样,它不讲究对称的哈(我是憨批🤡)

我们按照左边从矮到高的开始排列最长升序列, 再从右边从矮到高的开始排列最长降序列,它们之间第 i 同学的位置就是它们之间的分界线。 所以我们只要看从 第1位 到 第i位同学 能按顺序排几个, 再加上从 第i+1位 到 第N位同学 能按顺序排几个,它们就能形成合唱队列

那我们怎么快速找到某个同学在升序/降序中排第几位呢,我们需要一个数组给它们的排列计数,这里的计数是一个递推,例如在升序排序中,第c位同学比第b位同学高,我们只需要看第b位同学在排序中排第几位,第c位同学+1位即可,这里第c位同学要和他前面所有同学进行比较,才能看最多能排到第几位

那我们一边看代码一边解释吧..

三、AC 代码:


import java.io.*;
public class Main {
      // 输入流
	static StreamTokenizer sr = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))) ;
	public static void main(String[] args) {
		int N = getInt() ;
		int [] a = new int[N] ;
           // 同学们的位置是不能动的,把它存起来
		for( int i = 0 ; i < N ; i++ ) {
			a[i] = getInt() ;
		}
           // s1 是从左到右升序 k 位置的同学能排第几个
		int [] s1 = new int [N] ;
           // s2 是从右到左升序 k 位置的同学能排第几个
		int [] s2 = new int [N] ;
                
           // 从左到右升序
		for( int i = 0 ; i < N ; i++ ) {
			int max = 1 ;   // 从 i 同学开始排列,起码能站一个(它自己)
			for( int j = 0 ; j < i ; j++ ) {
                     // 如果i前面的同学 j 比它小,它就可以排在j后面,它的位置就是s1[j]+1 
				if( a[i]>a[j] )
					max = Math.max( s1[j]+1 , max ) ;
			}
			s1[i] = max ;
		}
           // 从右到左升序
		for( int i = N-1 ; i >= 0 ; i-- ) {
			int max = 1 ;
			for( int j = N-1 ; j > i ; j-- ) {
				if( a[i]>a[j] )
					max = Math.max( s2[j]+1 , max ) ;
			}
			s2[i] = max ;
		}
                
           //   第1~i位同学在升序这能站第几位的最大 + 第 i+1 ~ n 位同学在降序这能站第几位的最大
		int ans = 0 ;
		for( int i = 0 ; i < N ; i++ ) {
			for( int j = i+1 ; j < N ; j++ )
				ans = Math.max( s1[i]+s2[j] , ans) ;
		}
           // 总人数 - 留下了排列的人数 = 出列的人数
		ans = N - ans ;
		System.out.println( ans );
	}
	private static int getInt() {
		try {
			sr.nextToken() ;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return (int) sr.nval ;
	}

}

四、总结:

总的来说就是如何排列最长升序子序列和最长降序子序列,合唱队列的左边部分选最长升序子序列中的左边部分,合唱队列的右边部分选最长降序子序列中的右边部分(选的是该同学在最长升序子序列/最长降序子序列中排第几位),由于同学们的位置是固定的,我们就容易找出合唱队列最多能有多少人了,最少需要几位同学出列的答案也出来了~