描述:
某餐馆有n张桌子,每张桌子有一个参数:a 可容纳的最大人数; 有m批客人,每批客人有两个参数:b人数,c预计消费金额。 在不允许拼桌的情况下,请实现一个算法选择其中一部分客人,使得总预计消费金额最大。
编程题目:https://www.nowcoder.com/share/jump/6332849331742305693605
输入描述:
输入包括m+2行。 第一行两个整数n(1 <= n <= 50000),m(1 <= m <= 50000) 第二行为n个参数a,即每个桌子可容纳的最大人数,以空格分隔,范围均在32位int范围内。 接下来m行,每行两个参数b,c。分别表示第i批客人的人数和预计消费金额,以空格分隔,范围均在32位int范围内。
输出描述:
输出一个整数,表示最大的总预计消费金额
示例1
输入:
3 5 // 第一行:n桌子数,m客人批次数量
2 4 2 // 第二行:ai每个桌子可容纳的最大人数
1 3 // 接下来的m行,每行两个参数b,c
3 5 // b该批次的客人的人数,c该批次预计消费金额。
3 7
5 9
1 10输出:
20
思路(贪心+排序):
题目实际意思:在不允许拼桌且一个桌子只能分配一次的情况下,从m批客人中选择部分批次的客人安排就座,使得总预计消费金额最大,并非要安排所有批客人。如果该批次的人数超过所有桌子的可容纳的最大人数,则这个批次抛弃掉。
接收输入数据:
Scanner sc = new Scanner(System.in);
// 桌子数
int n = sc.nextInt();
// 客人批次数量
int m = sc.nextInt();
// 记录每张桌子的容纳最大人数
int[] tables = new int[n];
for (int i = 0; i < n; i++) {
tables[i] = sc.nextInt();
}
// 记录每批次的人数和预计消费金额
List<int[]> groups = new ArrayList<>();
for (int i = 0; i < m; i++) {
// 人数
int b = sc.nextInt();
// 预计消费金额
int c = sc.nextInt();
groups.add(new int[]{b,c});
}
- 第一行接收:
n
桌子数,m
客人批次数量 - 第二行接收:
ai
每个桌子可容纳的最大人数,使用int类型数组
存储数据 - 接下来的m行接收:
b
该批次的客人的人数,c
该批次预计消费金额,使用int类型数组
存储人数和预计消费金额,group[0]
代表人数,group[1]
代表预计消费金额,再整体存储在List
链表中方便后续进行遍历。可以进行客人人数和最大桌子容纳人数条件判断,只存储小于最大桌子容纳人数的客人批次,跳过无效数据的处理,优化一下时间。
处理数据:
// 对桌子按可容纳人数从小到大排序
Arrays.sort(tables);
// 对客人按消费金额从大到小排序
groups.sort((g1,g2)-> g2[1] - g1[1]);
// 总消费金额
long totalConsumption = 0;
// 标记被使用的桌子
boolean[] tableUsed = new boolean[n];
// 遍历每批客人
for (int[] group : groups) {
// 人数
int groupNum = group[0];
// 预计消费金额
int groupConsumption = group[1];
// 遍历寻找最小且合适的桌子
for(int i = 0; i < n; i++){
// 当桌子没有使用且可以容纳该批次的人数,安排对应的桌子,增加消费金额。
if(!tableUsed[i] && groupNum <= tables[i]){
// 增加消费金额
totalConsumption += groupConsumption;
// 标记桌子
tableUsed[i] = true;
// 安排后跳出循环
break;
}
}
}
// 打印结果
System.out.println(totalConsumption);
需要对桌子按可容纳人数从小到大排序
和对客人按消费金额从大到小排序
,应为要求最大的总预计消费金额
,所以希望消费金额大的客人尽可能使用最小的桌子(贪心)
。
外层遍历每批客人,从大到小遍历。内层遍历桌子为其寻找合适且最小的桌子,从小到大遍历。当寻找到没有被使用过且满足需求的桌子分配给该批次的客户,累加总消费金额。所以需要两个额外的变量boolean[] tableUsed = new boolean[n];
和long totalConsumption = 0;
,tableUsed
用于标记并且记录被使用过的桌子,totalConsumption
用于累加总消费金额。
注意:
为客人寻找合适桌子的判断条件:groupNum <= tables[i] && !tableUsed[i]
,不能将这两个条件交换成!tableUsed[i] && groupNum <= tables[i]
会导致在处理大规模测试用例时出现超时的情况,原因是交换后的条件判断存在无效过滤,不管这个桌子是否符合客人人数需求都进行桌子是否已被使用的判断,这样会浪费很多时间。
完整代码:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()){
// 桌子数
int n = sc.nextInt();
// 客人批次数量
int m = sc.nextInt();
// 记录每张桌子的容纳最大人数
int[] tables = new int[n];
for (int i = 0; i < n; i++) {
tables[i] = sc.nextInt();
}
// 对桌子按可容纳人数从小到大排序
Arrays.sort(tables);
// 记录每批次的人数和预计消费金额
List<int[]> groups = new ArrayList<>();
for (int i = 0; i < m; i++) {
// 人数
int b = sc.nextInt();
// 预计消费金额
int c = sc.nextInt();
// 只存储小于最大桌子容纳人数的客人批次
if (b <= tables[n - 1]) {
groups.add(new int[]{b,c});
}
}
// 对客人按消费金额从大到小排序
groups.sort((g1,g2)-> g2[1] - g1[1]);
// 总消费金额
long totalConsumption = 0;
// 标记被使用的桌子
boolean[] tableUsed = new boolean[n];
// 记录被使用的桌子数
int count = 0;
// 遍历每批客人
for (int[] group : groups) {
// 人数
int groupNum = group[0];
// 预计消费金额
int groupConsumption = group[1];
// 遍历寻找最小且合适的桌子
for(int i = 0; i < n; i++){
// 当桌子没有使用且可以容纳该批次的人数,安排对应的桌子,增加消费金额。
if(groupNum <= tables[i] && !tableUsed[i]){
totalConsumption += groupConsumption;
tableUsed[i] = true;
count++;
break;
}
}
//如果桌子被安排完了,那么其他的客人就不同再从堆里弹出了,已经没有机会了
if(count==n){
break;
}
}
System.out.println(totalConsumption);
}
sc.close();
}
优化思路:
上面的代码由于使用了嵌套循环遍历客人和桌子,时间复杂度达到了 O(m∗n)
,可以优化一下寻找合适桌子的遍历,修改为二分查找合适的桌子。
二分查找出来合适的桌子索引index
,可能被使用过,如果被使用过则index++
,寻找下一个桌子,直到找到没有被使用过的桌子。
二分查找:
// 二分查找
public static int binarySearch(int[] tables, int target) {
// 左索引
int left = 0;
// 右索引
int right = tables.length - 1;
// 结果索引
int result = tables.length;
while (left <= right) {
// 中间索引
int mid = left + (right - left) / 2;
// 当桌子的最大容纳人数大于或者等于目标人数时,更新结果索引。
// 缩小范围继续寻找尽可能小且符合要求的桌子。
if (tables[mid] >= target) {
result = mid;
right = mid - 1;
} else {
// target > tables[mid]
left = mid + 1;
}
}
return result;
}
这里的二分查找跟普通的二分查找有点不一样,这里桌子的最大容纳人数大于或者等于目标人数
就满足条件,但是不能马上返回结果,需要继续缩小范围查找尽可能小且满足需求的桌子。
优化后的完整代码:
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
// 桌子数
int n = sc.nextInt();
// 客人批次数量
int m = sc.nextInt();
// 记录每张桌子的容纳最大人数
int[] tables = new int[n];
for (int i = 0; i < n; i++) {
tables[i] = sc.nextInt();
}
// 对桌子按可容纳人数从小到大排序
Arrays.sort(tables);
// 记录每批次的人数和预计消费金额
List<int[]> groups = new ArrayList<>();
for (int i = 0; i < m; i++) {
// 人数
int b = sc.nextInt();
// 预计消费金额
int c = sc.nextInt();
// 只存储小于最大桌子容纳人数的客人批次
if (b <= tables[n - 1]) {
groups.add(new int[]{b, c});
}
}
// 对客人按消费金额从大到小排序
groups.sort((g1, g2) -> g2[1] - g1[1]);
// 总消费金额
long totalConsumption = 0;
// 标记被使用的桌子
boolean[] tableUsed = new boolean[n];
// 记录被使用的桌子数
int count = 0;
// 遍历每批客人
for (int[] group : groups) {
// 人数
int groupNum = group[0];
// 预计消费金额
int groupConsumption = group[1];
// 二分查找合适的桌子
int index = binarySearch(tables, groupNum);
// 如果桌子被使用了,往后寻找桌子
while (index < n && tableUsed[index]) {
index++;
}
// 判断索引是否合法
if (index < n) {
tableUsed[index] = true;
totalConsumption += groupConsumption;
count++;
}
//如果桌子被安排完了,那么其他的客人就不同再从堆里弹出了,已经没有机会了
if (count == n) {
break;
}
}
System.out.println(totalConsumption);
}
sc.close();
}
// 二分查找
public static int binarySearch(int[] tables, int target) {
// 左索引
int left = 0;
// 右索引
int right = tables.length - 1;
// 结果索引
int result = tables.length;
while (left <= right) {
// 中间索引
int mid = left + (right - left) / 2;
// 当桌子的最大容纳人数大于或者等于目标人数时,更新结果索引。
// 缩小范围继续寻找尽可能小且符合要求的桌子。
if (tables[mid] >= target) {
result = mid;
right = mid - 1;
} else {
// target > tables[mid]
left = mid + 1;
}
}
return result;
}
}