题目:
给你一个下标从 0 开始的整数数组 costs ,其中 costs[i] 是雇佣第 i 位工人的代价。
同时给你两个整数 k 和 candidates 。我们想根据以下规则恰好雇佣 k 位工人:
- 总共进行
k轮雇佣,且每一轮恰好雇佣一位工人。 - 在每一轮雇佣中,从最前面
candidates和最后面candidates人中选出代价最小的一位工人,如果有多位代价相同且最小的工人,选择下标更小的一位工人。- 比方说,
costs = [3,2,7,7,1,2]且candidates = 2,第一轮雇佣中,我们选择第4位工人,因为他的代价最小[3,2,7,7,1,2]。 - 第二轮雇佣,我们选择第
1位工人,因为他们的代价与第4位工人一样都是最小代价,而且下标更小,[3,2,7,7,2]。注意每一轮雇佣后,剩余工人的下标可能会发生变化。
- 比方说,
- 如果剩余员工数目不足
candidates人,那么下一轮雇佣他们中代价最小的一人,如果有多位代价相同且最小的工人,选择下标更小的一位工人。 - 一位工人只能被选择一次。
返回雇佣恰好
k位工人的总代价。
解决方法一:暴力破解
首先想到的是采用暴力破解的方法,在数组长度大于candidates时,遍历前candidates和后candidates个元素,之后手动维护数组的元素长度。当长度小于candidates时,直接遍历找到最小值即可,最终测试用例超时。
import java.util.Arrays;
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public long totalCost(int[] costs, int k, int candidates) {
long count = 0; //计算总花费
/*
因为这里我们一直开销一定是大于0的,因此当我们取出来元素后
*/
int number = 0; //雇佣工人的数量
int length = costs.length; //记录数组的实际长度
while (number != k) {
if (length > candidates) {
int min = costs[0]; //存储最小代价
int location = 0; //存储下标
for (int i = 0; i < candidates; i++) { //遍历找到最小值
if (costs[i] < min) {
min = costs[i];
location = i;
}
}
for (int i = length - candidates; i < length; i++) {
if (costs[i] < min) {
min = costs[i];
location = i;
}
}
count = count + min; //计算当前的花费
for (int i = location; i < length - 1; i++) { //修改数组的长度
costs[i] = costs[i + 1];
}
length--;
} else {
int min = 100001; //因为花费最大的是10000;这样设置设为了避免cost[0] = 0
int location = 0;
for (int i = 0; i < length; i++) {
if (costs[i] != 0 && costs[i] < min) {
min = costs[i];
location = i;
}
}
count = count + min;
costs[location] = 0;
}
number++;
}
return count;
}
}
方法二:优先队列
这种方法根据Java的优先队列来实现,优先级队列的头部是基于自然排序或基于比较器的排序的最小元素。当我们轮询队列时,它从队列中返回头对象。
import java.util.Arrays;
import java.util.PriorityQueue;
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public long totalCost(int[] costs, int k, int candidates) {
//创建优先队列1,主要存储0~candidates-1的元素
PriorityQueue<Integer>priorityQueue1 = new PriorityQueue<>();
//创建优先队列2,主要存储length-candidates的元素
PriorityQueue<Integer>priorityQueue2 = new PriorityQueue<>();
//这两个参数的主要作用是确保两个优先队列的元素不重复
int l = candidates-1; //指示优先队列1的尾部
int r = costs.length - candidates;//指示优先队列2的头部
long count = 0l; //计算总开销
//向优先队列1中存放元素
for(int i = 0;i < candidates; i++){
priorityQueue1.offer(costs[i]);
}
if(l>=r){ //这种情况下表示左candidates和右candidates部分有交集,要把交集去掉
for(int i = l+1; i<costs.length; i++){
priorityQueue2.offer(costs[i]);
}
}else{//此时无交集,直接从r插入
for(int i = r; i<costs.length; i++){
priorityQueue2.offer(costs[i]);
}
}
//现在优先队列创建完成了,我们只需要比较队头元素即可,因为优先队列从低到高有序
//当两个队列均不为空的时候执行该循环
while(k>0&& !priorityQueue1.isEmpty() && !priorityQueue2.isEmpty()){
if(priorityQueue1.peek()<=priorityQueue2.peek()){
count += priorityQueue1.poll();
//++l,是判断l的下一个元素在不在r里边
if(++l<r){//说明没有交集,删除后需要补偿至candidates大小
priorityQueue1.offer(costs[l]);
}
}else{
count += priorityQueue2.poll();
//--r,是判断r的前一个元素在不在l里边
if(--r>l){
priorityQueue2.offer(costs[r]);
}
}
k--; //此时已经选出来一个工人了
}
//此时表明人还没有选完
while(k>0){
if(!priorityQueue1.isEmpty()){
count += priorityQueue1.poll();
}
if(!priorityQueue2.isEmpty()){
count += priorityQueue2.poll();
}
k--;
}
return count;
}
}