从长度为n的数组arr中,挑选一个子序列,使得中位数最大。

从长度为n的数组arr中,挑选一个子序列,使得中位数最大。

提示:京东20220416笔试题2


题目

小明有一个长度为n的序列,他想从中挑选一个子序列,使得这个子序列的中位数最大。显然,小明只需要选择原序列中最大的那个数就行。【这意味着本题没啥挑战性了】
因此他加大难度,给出了一个限定:
原序列中相邻的两个数至少有一个被选到子序列中。
小明想知道在这个限制条件下,子序列最大的中位数是多少?
这里定义中位数是第n/2小的数,比如12345,中位数是3
234567中位数是4,就是上中位数

输入:第一行一个正整数T,表示数据组数
对于每一组数据,第一行输入一个正整数n,第二行输入n个正整数arri,表示原序列。2<=n<=3*10**4,1<=arri<=10^5,1<=T<=5.
输出,对于每一组数据,输出一行一个正整数,表示能够得到的最大的中位数。

样例输入:
3
5
1 2 3 4 5
6
1 2 3 4 5 6
2
7 8
样例输出:
4
4
8


一、审题

理解一下,原序列中相邻的两个数至少有一个被选到子序列中
比如:
123456789
要1的话,可以不要2,但下一次选择必须选中3,因为相邻的两个数23必须有一个在子序列中,2不要就得要3。

上面那个78为啥最后得8?因为8单独做子序列,则8就是最大的中位数。


二、解题

那么,以我们在L–R范围内任意挑选数,相邻俩数必有一个在子序列中,子序列放入有序数组set中,如果有size个元素,则中位数为set.get(size/2)即<=k这个数,不同的L–R范围,更新一个max中位数。

图1
算法大流程:
主函数proce中,0位置先决定要,还是不要,放入set中,set是有序的【收集我们要的子序列】,按照arri的value排序。
然后进入暴力递归函数func,func定义为
来到arr的i位置,i - 1位置在集合中吗【preYes:boolean】?决定要i还是不要i,要就放入set,之前求得的max业带着,这条路求得的最大中位数。
base case:当i==N之后,将收集好的那些要的位置,结算中位数。用set中的中位数,更新中位数max;
任何位置i怎么选?
1)如果i-1位置没要,则i位置必须要【yes】
2)如果i-1位置要了,则i位置可以要【yes】,也可以不要【no】

返回yes和no的最大值,即最大中位数。

暴力递归代码:

public static class Main {
        public static void main(String[] args) {
            Scanner in = new Scanner(System.in);
            // 注意 hasNext 和 hasNextLine 的区别
            //T次循环,别忘了--T
            int T = in.nextInt();
            while (T != 0){
                process();
                T--;
            }
        }

        //输入一组数据n个元素,紧跟第二行arr,就处理一个,输出最大中位数

        public static void process(){
            //输入
            Scanner in = new Scanner(System.in);
            int n = in.nextInt();
            Node[] arr = new Node[n];
            for (int i = 0; i < n; i++) {
                int value = in.nextInt();
                arr[i] = new Node(i, value);
            }

            //--子序列集合就不会重复了
            TreeSet<Node> set = new TreeSet<>(new valueComparator());
            //来到i位置,i - 1位置在集合中吗?决定要i还是不要i,放入set,当i==N之后更新中位数max
            //首先,要0位置,更新一次max
            int max = Integer.MIN_VALUE;//全局max负无穷
            set.add(arr[0]);
            max = Math.max(max, func(arr, true, 1, set, max));
            //其次不要0位置,更新一次max
            set = new TreeSet<>(new valueComparator());//恢复现场
            max = Math.max(max, func(arr,false, 1, set, max));

            System.out.println(max);
        }

        //来到i位置,i - 1位置在集合中吗?决定要i还是不要i,放入set,之前求得的max放入,当i==N之后更新中位数max
        public static int func(Node[] arr, boolean preYes, int i, TreeSet<Node> set, int max){
            if (i == arr.length){
                //排序结果,然后算出中位数,更新max
                //上中位数,12345取3,坐标5-1/2=2
                //234567取4,坐标6-1/2=2
                //

                int k = (set.size() - 1) >> 1;
                List<Node> list = new ArrayList<>(set);//方便找k
                return Math.max(max, list.get(k).value);//那个数就是n/2的中位数
            }

            //观察i-1位置要了吗
            int yes = Integer.MIN_VALUE;
            int no = Integer.MIN_VALUE;
            //这里之前要i-1,则i可要,可不要
            if (preYes) {
                no = func(arr, false, i + 1,  set, max);
                set.add(arr[i]);
                yes = func(arr, true, i + 1, set, max);
            }
            //这里之前i-1没要,则i必须要
            else {//跟上面条件是互斥的
                set.add(arr[i]);
                yes = func(arr, true, i + 1, set, max);
            }


            return Math.max(yes, no);
        }

        //有重复的元素,为了排序,整一个对象数组
        public static class Node{
            public int i;//位置
            public int value;//arri

            public Node(int idx, int v){
                i = idx;
                value = v;
            }
        }

        public static class valueComparator implements Comparator<Node> {
            @Override
            public int compare(Node o1, Node o2){
                return o1.value - o2.value;//按照值排序即可
            }
        }
    }

总结

提示:重要经验:

1)静下心来,利用所学知识,好好推演,写出暴力递归的代码,就能得到结果。
2)多练习互联网大厂的难题,难不要怕,可以快速提升能力,培养敏感度和coding能力;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值