1.寻找左侧边界的二分查找
某比赛已经进入了淘汰赛阶段,已知共有n名选手参与了此阶段比赛,他们的得分分别是a_1,a_2….a_n,小美作为比赛的裁判希望设定一个分数线m,使得所有分数大于m的选手晋级,其他人淘汰。
但是为了保护粉丝脆弱的心脏,小美希望晋级和淘汰的人数均在**[x,y]**之间。
显然这个m有可能是不存在的,也有可能存在多个m,如果不存在,请你输出-1,如果存在多个,请你输出符合条件的最低的分数线。
==解题:==使用二分搜索查找满足条件的左侧边界
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static int x;
public static int y;
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
x=sc.nextInt();
y=sc.nextInt();
sc.nextLine();
int[] score=new int[n];
for(int i=0;i<n;i++){
score[i]=sc.nextInt();
}
int res=binarySearch(score);
System.out.println(res);
}
public static int binarySearch(int[] score){
int count=0;//满足条件的m的个数
int n=score.length;
int left=0;
int right=n;
Arrays.sort(score);
//查找左边界
while(left<right){
int mid=left+(right-left)/2;
if(judge(score,mid)){
//满足则继续往左找
right=mid;
count+=1;
}else{
//不满足就继续向右找
left=mid+1;
}
}
return count==0?-1:score[left-1];
}
public static boolean judge(int[] score,int mid){
int leftsize=mid;
int rightsize=score.length-mid;
if(leftsize>=x && leftsize<=y && rightsize>=x && rightsize<=y){
return true;
}else{
return false;
}
}
}
2.贪心
我们称一个长度为n的序列为正则序列,当且仅当该序列是一个由1~n组成的排列,即该序列由n个正整数组成,取值在[1,n]范围,且不存在重复的数,同时正则序列不要求排序
有一天小团得到了一个长度为n的任意序列,他需要在有限次操作内,将这个序列变成一个正则序列,每次操作他可以任选序列中的一个数字,并将该数字加一或者减一。
请问他最少用多少次操作可以把这个序列变成正则序列?
==解题:==先将数组排序,排序之后每个位置的值是和自己当前所在位置索引差值的绝对值是最小的,将绝对值加和即是操作的最小次数
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
sc.nextLine();
int[] arr=new int[n];
for(int i=0;i<n;i++){
arr[i]=sc.nextInt();
}
Arrays.sort(arr);
int ans=0;//结果
for(int i=1;i<=n;i++){
ans+=Math.abs(i-arr[i-1]);
}
System.out.println(ans);
}
}
3.小顶堆(优先队列)
小美和小团所在公司的食堂有N张餐桌,从左到右摆成一排,每张餐桌有2张餐椅供至多2人用餐,公司职员排队进入食堂用餐。小美发现职员用餐的一个规律并告诉小团:当男职员进入食堂时,他会优先选择已经坐有1人的餐桌用餐,只有当每张餐桌要么空着要么坐满2人时,他才会考虑空着的餐桌;
当女职员进入食堂时,她会优先选择未坐人的餐桌用餐,只有当每张餐桌都坐有至少1人时,她才会考虑已经坐有1人的餐桌;
无论男女,当有多张餐桌供职员选择时,他会选择最靠左的餐桌用餐。现在食堂内已有若干人在用餐,另外M个人正排队进入食堂,小团会根据小美告诉他的规律预测排队的每个人分别会坐哪张餐桌。
==解题:==因为餐桌只有3种状态,所以建立3个小顶堆,分别表示3种状态的餐桌;男生优先选择1的小顶堆,如果1的小顶堆不为空,就弹出堆顶元素并压入2的小顶堆;如果为空,就弹出0的小顶堆的堆顶元素并压入1的小顶堆。女生优先选择0的小顶堆,如果0的小顶堆不为空,就弹出堆顶元素并压入1的小顶堆;如果为空,就弹出1的小顶堆的堆顶元素并压入2的小顶堆。
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
int T=Integer.parseInt(reader.readLine());
while (T>0){
T--;
int N=Integer.parseInt(reader.readLine());//数组长度
String tables = reader.readLine();
int M=Integer.parseInt(reader.readLine());
String MF=reader.readLine();
int[] res=helper(tables,MF);
for(int index:res){
writer.write(Integer.toString(index));
writer.newLine();
}
}
writer.flush();
}
public static int[] helper(String tables,String MF){
List<PriorityQueue<Integer>> pqs=new ArrayList<>(3);//定义三个小根堆
pqs.add(new PriorityQueue<>());
pqs.add(new PriorityQueue<>());
pqs.add(new PriorityQueue<>());
for (int i = 0; i < tables.length(); i++) {
pqs.get(tables.charAt(i)-'0').add(i);
}
int[] res=new int[MF.length()];
for (int i = 0; i < MF.length(); i++) {
int table;
if(MF.charAt(i)=='M'){
if(pqs.get(1).isEmpty()){
table= pqs.get(0).poll();//从0的小顶堆中弹出
pqs.get(1).add(table);//放入1的小顶堆中
}else{
table=pqs.get(1).poll();//从1的小顶堆中弹出
pqs.get(2).add(table);//放入2的小顶堆中
}
}else{
if (pqs.get(0).isEmpty()){
table=pqs.get(1).poll();//从1的小顶堆中弹出
pqs.get(2).add(table);//放入2的小顶堆中
}else{
table= pqs.get(0).poll();//从0的小顶堆中弹出
pqs.get(1).add(table);//放入1的小顶堆中
}
}
res[i]=table+1;
}
return res;
}
}
4.分治重构二叉树+记忆化搜索
小团有一个由N个节点组成的二叉树,每个节点有一个权值。定义二叉树每条边的开销为其两端节点权值的乘积,二叉树的总开销即每条边的开销之和。小团按照二叉树的中序遍历依次记录下每个节点的权值,即他记录下了N个数,第i个数表示位于中序遍历第i个位置的节点的权值。之后由于某种原因,小团遗忘了二叉树的具体结构。在所有可能的二叉树中,总开销最小的二叉树被称为最优二叉树。现在,小团请小美求出最优二叉树的总开销。
==解题:==要寻找开销最小的二叉树,只能重构出所有符合该中序遍历条件的二叉树并计算他们的开销,开销最小值就是该问题的解。查找的过程就是一个递归的过程,该过程中会将每个节点作为根节点构建二叉树并计算开销,为了提高效率,使用记忆化搜索,即将已经计算完开销的二叉树的开销存储到一个数组中。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
static int N;
static int[] weight;//各节点权值
static int[][][] memo;//mem[l][r][k]表示以weight[l:r]为子节点,以weight[k]为根节点的树开销
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
N = Integer.parseInt(reader.readLine().trim());
String[] Nodes = reader.readLine().trim().split(" ");//按空格分开
weight=new int[N];
for (int i = 0; i < N; i++) {
weight[i] = Integer.parseInt(Nodes[i]);
}
memo = new int[N][N][N];
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
for (int k = 0; k < N; k++) {
memo[i][j][k] = -1;//全都初始化为-1,为了后面是否存到memo中进行判断
}
}
}
//一开始left为0,right为N-1,root为-1(表示根节点)
System.out.println(helper(0, N - 1, -1));
}
//分治重构二叉树+记忆化搜索:遍历以每个节点构建二叉树,计算总开销
public static int helper(int left,int right,int root){
//越界判断
if(left>right){
return 0;
}
//记忆化搜索
if(root>=0 && memo[left][right][root]!=-1){
return memo[left][right][root];
}
int cost=Integer.MAX_VALUE;
int leftCost=0,rightCost=0;
for(int i=left;i<=right;i++){
leftCost=helper(left,i-1,i);//左子树开销
rightCost=helper(i+1,right,i);//右子树开销
//root=-1时为最上面的根节点,已经是根节点了,不能再和父节点相乘计算花销
cost=Math.min(cost,leftCost+rightCost+weight[i]*(root!=-1?weight[root]:0));
}
//只有根节点下面的这些节点才能计算花销,因为计算是从子节点往上和其父节点的权值相乘,根节点没法计算
if(root>=0){
memo[left][right][root]=cost;
}
return cost;
}
}