Java机试题*:合唱队(动态规划、加强版最长子序列问题)

描述

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

说明:

N 位同学站成一排,音乐老师要请其中的 (N - K) 位同学出列,使得剩下的 K 位同学排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为 1,2…,K ,他们的身高分别为 T1,T2,…,TK ,   则他们的身高满足存在 i (1<=i<=K) 使得 T1<T2<......<Ti-1<Ti>Ti+1>......>TK 。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
 
注意:不允许改变队列元素的先后顺序 且 不要求最高同学左右人数必须相等
请注意处理多组输入输出!
 
数据范围: 1 \le n \le 3000 \1n3000 
 

输入描述:

有多组用例,每组都包含两行数据,第一行是同学的总数 N ,第二行是 N 位同学的身高,以空格隔开

输出描述:

最少需要几位同学出列

import java.util.*;

/**
 * 
   *  思路:1.构建整个合唱队人数。2.获取每个位置,左边最长递增数列、右边递减数列。3.左右数列相加则为总的最大数列,用总人数减之,则是最少要去掉的人数。
   *  关键点1:left[0] = 1; right[num - 1] = 1; 故左边递增因从0开始,右边需要从num - 1开始。
   *  关键点2:left[i] = 1; right[i] = 1;每个位置初始化长度都是自己本身故为1.
   *  关键点3:状态转移条件:singer[j] < singer[i],left[i] = Math.max(left[j] + 1, left[i]),因为是按数列关键点1顺序遍历,所以j的长度是确定的,又因为i是最高的,故i相对于j构成的数列加上i自身,必定比j大1;
 */
public class Main {
    public static int singer[];
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while (in.hasNextInt()) {
            int num = in.nextInt();
            // 合唱团总人数
            int[] singer = new int[num];
            for (int i = 0; i < num; i++) {
                singer[i] = in.nextInt();        
            }
            // 以每个位置作为最高位置获取左侧最长的递增
            // 注意:因为左边数列,left[0]的最长数列是1为固定的长度,所以需要从0开始遍历。
            // 右侧递减数列,是num -1是1,是确定的所以从num-1开始遍历,顺序不能改变,否则得到的数列长度是错的,
            int[] left = new int[num];
            left[0] = 1; // 左边0位置,长度初始化为1
            for (int i = 0; i < num; i++) {
                // 每个位置,数列初始长度为1
                left[i] = 1;
                for (int j = 0; j < i; j++) {
                    // 符合条件
                    if(singer[j] < singer[i]) {
                        // left[j]表示j位置最长递增,left[j] + 1表示:因为i位置身高大于j,故对于i来说,至少比j处大1
                        // 使用left[j] + 1,与left[i]之前的结果动态比较取最大。
                        left[i] = Math.max(left[j] + 1, left[i]);
                    }
                }
            }
            
            // 以每个位置作为最高位置获取右侧最长的递减
            int[] right = new int[num];
            right[num - 1] = 1; // 右边0位置,长度初始化为1
            for (int i = num - 1; i >= 0; i--) {
                // 每个位置,数列初始长度为1
                right[i] = 1;
                for (int j = num - 1; j > i; j--) {
                    // 符合条件
                    if(singer[j] < singer[i]) {
                        right[i] = Math.max(right[j] + 1, right[i]);
                    }
                }
            }
            
            // 左右合并
            int[] ret = new int[num];
            for (int i = 0; i < num; i++) {
                // 左右数列都算了本身,所以需要减1
                ret[i] = left[i] + right[i] - 1;
            }
                    
            // 找到最大的数列,即是去掉人数最少的情况
            int max = ret[0];
            for (int i = 1; i < num; i++) {
                if(max < ret[i]) {
                    max = ret[i];
                }
            }
            System.out.println(num - max);
        }
    }
    

}

题目来源:牛客网

参考链接:https://blog.nowcoder.net/n/03e8c2d251294f4fa649bd4c5a5c0f7b

posted @ 2022-01-19 14:35  对月当歌  阅读(212)  评论(0)    收藏  举报