华为笔试-超简单看懂合唱队问题

合唱队

问题描述:

计算最少出列多少位同学,使得剩下的同学排成合唱队形。

说明:

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位

同学排成合唱队形。 
合唱队形是指这样的一种队形:

设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK,   

则他们的身高满足存在i(1<=i<=K)

使得T1<T2<......<Ti-1< Ti >Ti+1>......>TK。 
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

 

输入描述:

 

整数N

输出描述:

 

最少需要几位同学出列

示例1

输入

复制

8
186 186 150 200 160 130 197 200

输出

复制

4

 

 

问题解析

算法原理:动态规划

涉及概念:最长递增子序列

解题思路 :在N位同学中找出一个同学i,经过去除几位同学后,使得i为一个分割点,使得i之前的同学身高都小于i,i之后的同学身高都大于i。

若使去除的同学最少,则留下的同学应最大。

解题说明:

取示例 A = {186, 186, 150, 200, 160, 130, 197, 200}

(1)首先对A中所有的元素,计算每个元素在其正向最长递增子序列中的位置:

例如A[4] = 160,其所在最长子序列为{150,160,197,200},故所在位置为2。

最长子序列计算方法见上篇文章:超简单看明白如何求最长子序列-动态规划

计算结果如下:

 

i01234567
A[i]186186150200160130197200
位置11122134

(2)对A中的所有元素,计算每个数在反向递增子序列中的位置,首先对A进行逆序排列,再计算,

计算结果如下:

 

i01234567
A[i]186186150200160130197200
位置33232111

(3)对于A中每个元素i:

计算其位于正向递增子序列中的位置,则表示位于i前面,小于A[i]且保持递增的有多少个元素;

计算其位于反射递增子序列中的位置,则表示位于i后面,大于A[i]且保持递减的有多少个元素;

将每个元素所在正向递增子序列中的位置+所在反向递减子序列中的位置相加,取最大者对应的下标i,即为所求的分界点i。

计算结果如下:

 

i01234567
A[i]186186150200160130197200
正向位置11122134
反向位置33232111
正向累加44354245

取最大值i = 4,此时该元素所在队列的长度即为 5 - 1 = 4。

减1说明:

由于正反累加时,当前元素计算2次,例如当i = 4时,对于160这个数,

                            所在递增序列为{150,160,,,},位置为2

                            所在反向递增序列为{130,160,,,},位置为2

                            若取i = 4为分界点,则160所在的队列为{150,160,130},队列长度为 2 + 2 - 1 = 3。

(4)因此需要出队的人数 = 总人数 - 队列长度

针对本示例,应为 8 - (5 - 1) = 4。

代码如下:

int main()
{
	
	int n = 0;
	while(cin>>n)
	{
		int m = 0;
		vector<int> dp1(n,1);//记录正向位置
		vector<int> dp2(n,1);//记录反向位置
		vector<int> vecints;
		for(unsigned int i = 0; i < n; i++)
		{
			cin >> m;
			vecints.push_back(m)
		}
		//计算正向位置
		for(unsigned int i = 0; i < n; i++)
		{
			for(unsigned int j = 0; j < i; j++)
			{
				if(vecints[j] < vecints[i] && dp1[j] >= dp1[i])
					dp1[i] = dp1[j] + 1;
			}
		}
		//反转序列
		reverse(vecints.begin(),vecints.end());
		//计算反向位置
		for(unsigned int i = 0; i < n; i++)
		{
			for(unsigned int j = 0; j < i; j++)
			{
				if(vecints[j] < vecints[i] && dp2[j] >= dp2[i])
					dp2[i] = dp2[j] + 1;
			}
		}
		//反转位置
		reverse(dp2.begin(),dp2.end());
		int Max = -1;//记录最大数列
		for (unsigned int i = 0; i < n; i++)
		{
			Max = Max < dp1[i] + dp2[i] ? dp1[i] + dp2[i] : Max;
		}
		cout << n - Max + 1 << endl;
	}
	return 0;
}

### 回答1: 题目描述: 给定一个长度为 n 的同学身高序列,要求在其中取出 kk 位同学排成合唱队形,要求先从左往右依次选择下标递增的 kk 位同学,然后从右往左依次选择下标递减的 kk 位同学,且中间位置的同学既能属于左边也能属于右边,求最多能有多少位同学同时在合唱队里,输出这个最大值。 解题思路: 针对这道题,可以采用动态规划的思路来解决。动态规划决策:以第 ii 个同学为结尾的最长上升子序列长度 Lis(i),以第 ii 个同学为开头的最长下降子序列长度 Lds(i),加起来再减去 11(因为 ii 这个同学被重复计算了一次)即为以 ii 为山顶信息的 LDS 长度。其状态转移方程为: Lis(i)=max(Lis(j))+1,j<i,a[i]>a[j]; 其中 a[i]>a[j] 前提是整个序列是上升的。如果 a[i]>a[j] 条件不满足的话,那么 Lis(i)=1。 Lds(i)=max(Lds(j))+1,j>i,a[i]>a[j] 其中 a[i]>a[j] 前提是整个序列是下降的。如果 a[i]>a[j] 条件不满足的话,那么 Lds(i)=1。 最后,遍历所有的 ii 并求出以第 ii 个同学为山顶的 LDS 长度即可。 实现过程中需要注意的点: - 在实现 Lis 和 Lds 函数时需要注意,需要将第一项设为 1,因为一个人也能组成一个队伍。 - 动态规划实现时需要用到两个数组,分别记录 Lis 和 Lds 的结果。 参考代码: ### 回答2: 这道题可以用动态规划来解决。设数组up[i]表示以i结尾的最长上升子序列长度,数组down[i]表示以i开始的最长下降子序列长度,则最终的答案为max{up[i]+down[i]-1}(1<=i<=n)。 首先,我们可以先计算出所有以i结尾的最长上升子序列长度up[i]。考虑到求以i结尾的最长上升子序列,我们可以从1~i-1枚举j,如果a[j]<a[i],则up[i]=max{up[i],up[j]+1}。这个过程可以使用动态规划来实现,时间复杂度为O(n^2)。 然后,我们再计算出所有以i开始的最长下降子序列长度down[i]。考虑到求以i开始的最长下降子序列,我们可以从n到i+1枚举j,如果a[j]<a[i],则down[i]=max{down[i],down[j]+1}。同样,这个过程也可以使用动态规划来实现,时间复杂度也为O(n^2)。 最终,我们可以从1~n枚举i,求出up[i]+down[i]-1的最大值,即为答案。 此外,题目中要求出列的同学人数,可以使用贪心算法,从up[i]+down[i]-1最大的位置i开始,先选择up[i]最大的k位同学出列,再选择down[i]最大的k位同学出列,总共出列2k位同学。之后剩下的同学即可排成合唱队形。 总体时间复杂度为O(n^2)。 ### 回答3: 这是一个典型的动态规划问题。首先需要明确状态。我们可以将状态定义为:dp[i][j]表示以第i名学生为结尾,已经选择j个学生出列,所得到的合唱队形的最大长度。 然后考虑状态转移方程。对于第i名学生,有两种选择:出列或不出列。如果第i名学生不出列,则dp[i][j]可以由dp[i-1][j]转移而来。如果第i名学生出列,则需要考虑所有比他低的学生中已经出列的人数,假设为p,则dp[i][j]可以由max{dp[k][j-p]+1}(1≤k<i)转移而来。 最终答案为所有dp[i][j]中最大的值。 需要注意的是,一定要先将所有学生按身高排序,这样才能满足合唱队形的定义。同时,合唱队形的长度应该大于等于2,因此出列的人数j应该在1~n-1之间。 代码如下: (其中INF表示一个足够大的数,用于初始化)
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值