从长度为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中位数。
算法大流程:
主函数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能力;