题解 | #合唱队#

合唱队

http://www.nowcoder.com/practice/6d9d69e3898f45169a441632b325c7b4

题意整理。

  • N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
  • 假设K位同学均有对应的身高,合唱队形是指这K位同学的身高先严格递增,再严格递减。

方法一(动态规划)

1.解题思路

这道题本质上是求最长递增子序列,可以通过动态规划来做。只要先把每个位置结尾的最长递增子序列长度计算出来,再逆序遍历数组,求每个位置结尾的最长递增子序列长度(如果是正序,则是该位置开头的最长递减子序列长度),将两者相加再减1即是对应位置的最长合唱队长度。统计所有位置的最长合唱队即可。

  • 状态定义:dp1[i]dp1[i]表示该位置结尾的最长递增子序列长度,dp2[i]dp2[i]表示该位置开头的最长递减子序列长度。
  • 状态初始化:初始长度均为1。
  • 状态转移:正序遍历时,如果i位置同学身高大于j位置同学,则可以排在j位置同学后面,即dp1[i]=Math.max(dp1[i],dp1[j]+1)dp1[i]=Math.max(dp1[i],dp1[j]+1)。逆序遍历时,如果i位置同学身高大于j位置同学,则j可排在i同学后面,构成递减子序列,即dp2[i]=Math.max(dp2[i],dp2[j]+1)dp2[i]=Math.max(dp2[i],dp2[j]+1)

2.代码实现

import java.util.Scanner;
import java.util.Arrays;

public class Main{
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        while(sc.hasNext()){
            //同学的总数
            int n=sc.nextInt();
            //N位同学的身高
            int[] arr=new int[n]; 
            for(int i=0;i<n;i++){
                arr[i]=sc.nextInt();
            }
            
            //dp1[i]表示该位置结尾的最长递增子序列长度
            int[] dp1=new int[n];
            //给dp1数组赋初值
            Arrays.fill(dp1,1);
            for(int i=0;i<n;i++){
                for(int j=0;j<i;j++){
                    //如果i位置同学身高大于j位置同学,则可以排在j位置同学后面
                    if(arr[j]<arr[i]){
                        dp1[i]=Math.max(dp1[i],dp1[j]+1);
                    }
                }
            }
            
            //dp2[i]表示该位置开头的最长递减子序列长度
            int[] dp2=new int[n];
            //给dp2数组赋初值
            Arrays.fill(dp2,1);
            for(int i=n-1;i>=0;i--){
                for(int j=i+1;j<n;j++){
                    /*如果i位置同学身高大于j位置同学,则可以排在j位置同学后面
                    反过来,则是j可排在i同学后面,构成递减子序列*/
                    if(arr[j]<arr[i]){
                        dp2[i]=Math.max(dp2[i],dp2[j]+1);
                    }
                }
            }
            
            //统计每个位置的合唱队形长度最大值
            int max=0;
            for(int i=0;i<n;i++){
                max=Math.max(max,dp1[i]+dp2[i]-1);
            }
            
            System.out.println(n-max);
        }
    }
}

3.复杂度分析

  • 时间复杂度:状态转移过程中,两层循环总共执行n(n1)/2n*(n-1)/2次,所以时间复杂度为O(n2)O(n^2)
  • 空间复杂度:需要额外大小为n的dp1数组和dp2数组,所以空间复杂度为O(n)O(n)

方法二(二分优化)

1.解题思路

  • 新建dp1数组和dp2数组,dp1[i]dp1[i]表示该位置结尾的最长递增子序列长度,dp2[i]dp2[i]表示该位置开头的最长递减子序列长度。
  • 然后维护一个递增数组tail1,如果tail1数组为空,或arr[i]大于tail1数组末尾元素,直接将arr[i]放在tail1数组末尾,tail1数组的长度即是当前位置结尾的最长递增子序列长度。否则,二分法找到arr[i]在tail1数组中的位置,假设该位置为l,则l+1为当前位置结尾的最长递增子序列长度。
  • 对于dp2数组,逆序遍历arr数组,维护一个递增数组tail2,如果tail2数组为空,或arr[i]大于tail2数组末尾元素,直接将arr[i]放在tail2数组末尾,tail2数组的长度即是当前位置开头的最长递减子序列长度。否则,二分法找到arr[i]在tail2数组中的位置,假设该位置为l,则l+1为当前位置开头的最长递减子序列长度。
  • 最后,遍历dp1、dp2数组,统计每个位置的合唱队形长度最大值。

动图展示(只考虑dp1数组): alt

2.代码实现

import java.util.Scanner;
import java.util.Arrays;

public class Main{
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        while(sc.hasNext()){
            //同学的总数
            int n=sc.nextInt();
            //N位同学的身高
            int[] arr=new int[n]; 
            for(int i=0;i<n;i++){
                arr[i]=sc.nextInt();
            }
            
            //dp1[i]表示该位置结尾的最长递增子序列长度
            int[] dp1=new int[n];
            //tail1表示维护的一个严格递增子序列
            int[] tail1=new int[n];
            //tail1数组的最后一个元素下标
            int end1=-1;
            for(int i=0;i<n;i++){
                //如果tail1数组为空,或arr[i]大于tail1数组末尾元素,直接将arr[i]放在tail1数组末尾
                if(i==0||tail1[end1]<arr[i]){
                    tail1[++end1]=arr[i];
                    dp1[i]=end1+1;
                }
                //否则,二分法找到arr[i]在tail1数组中的位置
                else{
                    int l=0;
                    int r=end1;
                    while(l<r){
                        int mid=l+(r-l)/2;
                        if(tail1[mid]>=arr[i]){
                            r=mid;
                        }
                        else{
                            l=mid+1;
                        }
                    }
                    tail1[l]=arr[i];
                    dp1[i]=l+1;
                }
                
            }
            
            //dp2[i]表示该位置开头的最长递减子序列长度
            int[] dp2=new int[n];
            //tail2表示维护的一个严格递增子序列
            int[] tail2=new int[n];
            //tail2数组的最后一个元素下标
            int end2=-1;
            for(int i=n-1;i>=0;i--){
                //如果tail2数组为空,或arr[i]大于tail2数组末尾元素,直接将arr[i]放在tail2数组末尾
                if(i==n-1||tail2[end2]<arr[i]){
                    tail2[++end2]=arr[i];
                    dp2[i]=end2+1;
                }
                //否则,二分法找到arr[i]在tail2数组中的位置
                else{
                    int l=0;
                    int r=end2;
                    while(l<r){
                        int mid=l+(r-l)/2;
                        if(tail2[mid]>=arr[i]){
                            r=mid;
                        }
                        else{
                            l=mid+1;
                        }
                    }
                    tail2[l]=arr[i];
                    dp2[i]=l+1;
                }
                
            }
            
            //统计每个位置的合唱队形长度最大值
            int max=0;
            for(int i=0;i<n;i++){
                max=Math.max(max,dp1[i]+dp2[i]-1);
            }
            
            System.out.println(n-max);
        }
    }
}

3.复杂度分析

  • 时间复杂度:总共有两层循环,内层循环为二分查找方式,时间复杂度为O(log2n)O(log_2n),所以时间复杂度为O(nlog2n)O(n*log_2n)
  • 空间复杂度:需要额外大小为n的dp1、dp2数组和tail1、tail2数组,所以空间复杂度为O(n)O(n)
xqxls的题解 文章被收录于专栏

牛客题解

全部评论

相关推荐

10-15 15:00
潍坊学院 golang
跨考小白:这又不是官方
投递拼多多集团-PDD等公司10个岗位
点赞 评论 收藏
分享
Yushuu:你的确很厉害,但是有一个小问题:谁问你了?我的意思是,谁在意?我告诉你,根本没人问你,在我们之中0人问了你,我把所有问你的人都请来 party 了,到场人数是0个人,誰问你了?WHO ASKED?谁问汝矣?誰があなたに聞きましたか?누가 물어봤어?我爬上了珠穆朗玛峰也没找到谁问你了,我刚刚潜入了世界上最大的射电望远镜也没开到那个问你的人的盒,在找到谁问你之前我连癌症的解药都发明了出来,我开了最大距离渲染也没找到谁问你了我活在这个被辐射蹂躏了多年的破碎世界的坟墓里目睹全球核战争把人类文明毁灭也没见到谁问你了😆
点赞 评论 收藏
分享
13 3 评论
分享
牛客网
牛客企业服务