有趣的算法题之合唱队

合唱队

N 位同学站成一排,音乐老师要请最少的同学出列,使得剩下的 K 位同学排成合唱队形。

设 𝐾 位同学从左到右依次编号为 1,2…,K ,他们的身高分别为 𝑇1 , 𝑇2 ,…, 𝑇𝐾 ,若存在 𝑖(1 ≤ 𝑖 ≤ 𝐾) 使得 𝑇1 < 𝑇2 < .....< 𝑇𝑖−1 < 𝑇𝑖 且 𝑇𝑖 > 𝑇𝑖+1 >......> 𝑇𝐾 ,则称这 𝐾 名同学排成了合唱队形。

通俗来说,能找到一个同学,他的两边的同学身高都依次严格降低的队形就是合唱队形。

例子:

123 124 125 123 121 是一个合唱队形

123 123 124 122 不是合唱队形,因为前两名同学身高相等,不符合要求

123 122 121 122 不是合唱队形,因为找不到一个同学,他的两侧同学身高递减。

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

注意:不允许改变队列元素的先后顺序  不要求最高同学左右人数必须相等

原题链接: 合唱队_牛客题霸_牛客网 (nowcoder.com)

格式

输入描述:用例两行数据,第一行是同学的总数 N ,第二行是 N 位同学的身高,以空格隔开

输出描述:最少需要几位同学出列

数据范围:1 ≤ n ≤ 3000 

样例

输入:8

           186 186 150 200 160 130 197 200

输出:4

说明:最终剩下的队列为 186 200 160 130 或 150 200 160 130

思路分析

问题转化

        从题目要求来看,我们的目标是最小化需要出列的学生人数,以便剩下的学生能够排成合唱队形。由于总人数是固定的,我们可以将这个问题转化为一个等价问题:找到最大的合唱团人数。这是因为如果知道了最大的合唱团人数,我们就可以通过总人数减去它来得到需要出列的学生人数。

合唱队形特点分析

        接下来,我们思考如何确定一个合唱团的人数。根据合唱队形的定义,存在一个中心点,使得该点左边的学生身高严格单调递增,而右边的学生身高严格单调递减。这意味着我们需要找到每个学生作为中心点时,左边递增序列和右边递减序列的长度,然后将它们相加,再加上中心点本身,就可以得到以该学生为中心的合唱团人数。

动态规划求解

        然而,对于每个学生,我们都需要重新计算其左边递增序列和右边递减序列的长度,这会导致大量的重复计算。我们可以发现,这个问题具有重叠子问题的性质,即计算某个学生的递增序列或递减序列长度时,可能会用到之前已经计算过的其他学生的序列长度。因此,我们可以采用动态规划的方法来避免重复计算,并快速求解。

        具体来说,我们可以使用两个一维数组 dpLeft 和 dpRight 来分别存储每个学生作为结尾的递增序列长度和作为开头的递减序列长度。首先,我们遍历所有学生,从左到右计算 dpLeft 数组,即每个学生左边递增序列的长度。然后,我们再次遍历所有学生,从右到左计算 dpRight 数组,即每个学生右边递减序列的长度。

在计算 dpLeft 数组时,对于每个学生,我们遍历其左边的所有学生,如果当前学生的身高大于前面某个学生的身高,说明可以将当前学生加入到以该学生结尾的递增序列中,因此更新 dpLeft 数组中的值。类似地,在计算 dpRight 数组时,我们遍历每个学生的右边所有学生,如果当前学生的身高大于后面某个学生的身高,说明可以将当前学生加入到以该学生开头的递减序列中,因此更新 dpRight 数组中的值。

        最后,我们遍历所有学生,将每个学生作为中心点,计算以该学生为中心的合唱团人数,即dpLeft[ i ] + dpRight[ i ] + 1(加上1表示中心点本身)。我们记录最大的合唱团人数,并将其与总人数相减,即可得到需要出列的最少学生人数。

代码

import java.util.Scanner;
import java.util.*;

public class Main {


    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt(); // 总人数
        int[] arr = new int[n]; // 身高数据
        int[][] dp = new int[n][2]; // 左右两边递增序列长度数据

        for (int i = 0; i < n; i++) {
            arr[i] = in.nextInt();
        }

        // 对第 i 个人跟他左边所有人比较
        for (int i = 1;i < n;i++) {
            for (int j = 0;j < i;j++) {
                // 若比左边某人高,说明找到一个截止至第 i 个人的递增序列
                if (arr[i] > arr[j]) {
                    // 动态规划存储递增序列
                    dp[i][0] = Math.max(dp[j][0] + 1, dp[i][0]);
                }
            }
        }

        // 对第 i 个人跟他右边所有人比较
        for (int i = n-2;i >= 0;i--) {
            for (int j = n-1;j > i;j--) {
                // 若比右边某人高,说明找到一个从第 i 个人开始的递减序列
                if (arr[i] > arr[j]) {
                    // 动态规划存储递减序列
                    dp[i][1] = Math.max(dp[j][1] + 1, dp[i][1]);
                }
            }
        }

        int ans = 0;
        // 以每个人为合唱团核心,找到最大的合唱团人数,就可以推出需要出列的最少同学人数
        for (int i = 0;i < n;i++) {
            int cur = dp[i][1] + dp[i][0] + 1;
            ans = Math.max(cur, ans);
        }
        System.out.println(n - ans);
    }
}

结果

最后


 

如果有所收获请点赞支持一下作者哦,您的点赞是我持续创作的动力!! 

  • 31
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值