给你一个下标从 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
位工人的总代价。
输入:costs = [17,12,10,2,7,2,11,20,8], k = 3, candidates = 4 输出:11 解释:我们总共雇佣 3 位工人。总代价一开始为 0 。 - 第一轮雇佣,我们从 [17,12,10,2,7,2,11,20,8] 中选择。最小代价是 2 ,有两位工人,我们选择下标更小的一位工人,即第 3 位工人。总代价是 0 + 2 = 2 。 - 第二轮雇佣,我们从 [17,12,10,7,2,11,20,8] 中选择。最小代价是 2 ,下标为 4 ,总代价是 2 + 2 = 4 。 - 第三轮雇佣,我们从 [17,12,10,7,11,20,8] 中选择,最小代价是 7 ,下标为 3 ,总代价是 4 + 7 = 11 。注意下标为 3 的工人同时在最前面和最后面 4 位工人中。 总雇佣代价是 11 。
输入:costs = [1,2,4,1], k = 3, candidates = 3 输出:4 解释:我们总共雇佣 3 位工人。总代价一开始为 0 。 - 第一轮雇佣,我们从 [1,2,4,1] 中选择。最小代价为 1 ,有两位工人,我们选择下标更小的一位工人,即第 0 位工人,总代价是 0 + 1 = 1 。注意,下标为 1 和 2 的工人同时在最前面和最后面 3 位工人中。 - 第二轮雇佣,我们从 [2,4,1] 中选择。最小代价为 1 ,下标为 2 ,总代价是 1 + 1 = 2 。 - 第三轮雇佣,少于 3 位工人,我们从剩余工人 [2,4] 中选择。最小代价是 2 ,下标为 0 。总代价为 2 + 2 = 4 。 总雇佣代价是 4 。
本人题解是这个,在最后132个用例后出现超时。
class Solution {
public long totalCost(int[] costs, int k, int candidates) {
int min_index=0,min_index_pre=0,min_index_last=costs.length;
long sum=0;
int len=costs.length;
while(k>0){
//如果candidates的数量小于costs数组
if(len>=candidates){
//前candidates的数量
min_index_pre=minCost(costs,candidates,len,false);
//后candidates的数量
min_index_last=minCost(costs,candidates,len,true);
if(costs[min_index_pre]<=costs[min_index_last]){
sum+=costs[min_index_pre];
costs=updateArray(costs,min_index_pre);
len--;
k--;
}
else{
sum+=costs[min_index_last];
costs=updateArray(costs,min_index_last);
len--;
k--;
}
}
//若costs数组人数少于candidates,选择最少的
else{
min_index=minCost(costs,len,len,false);
sum+=costs[min_index];
costs=updateArray(costs,min_index);
len--;
k--;
}
}
return sum;
}
//调整数组下标
public int[] updateArray(int[] array,int index){
int temp=array[index];
for(int i=index;i<array.length-1;i++){
array[i]=array[i+1];
}
array[array.length-1]=temp;
return array;
}
/**根据传入数组的长度,寻找数组最小的下标
*/
public int minCost(int[] array,int num,int length,boolean flag){
int min,index,array_num;
if(flag==false){
min=array[0];
index=0;
for(int i=1;i<num;i++){
if(min>array[i]){
min=array[i];
index=i;
}
}
}
else{
min=array[length-1];
index=length-1;
array_num=length-1-num;
for(int i=length-1;i>array_num;i--){
if(min>array[i]){
min=array[i];
index=i;
}
}
}
return index;
}
}
官方解法 :通过构造小根堆,通过把前candidates个元素和后candidates个元素加入构造小根堆,不断通过获取堆的根顶元素来获取最小代价。
小根堆:即先把元素插入时,按照完全二叉树来插入。每插入一个元素,则通过比对父节点和子节点的代价和序号来进行排列,代价和序号较小的则为父节点,并通过交换位置不断构造。
以costs =[17,12,10,2,7,2,11,20,8],k=3,candidates=4为例子:刚开始构造的最小堆为下:
public static long totalCost(int[] costs, int k, int candidates) {
int n = costs.length;
//建立小根堆,堆里每一个元素是数组,数组第一个是代价,第二个是costs的序号
//加入元素时,会按照小根堆的方式构造小根堆:先比较代价,后比较序号
PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);
int left = candidates - 1, right = n - candidates;
//前后candidates的元素个数小于总元素个数
if (left + 1 < right) {
//取前candidates元素加入堆
for (int i = 0; i <= left; ++i) {
pq.offer(new int[]{costs[i], i});
}
//取后candidates元素加入堆
for (int i = right; i < n; ++i) {
pq.offer(new int[]{costs[i], i});
}
}
//前后candidates的元素个数大于总元素个数,相当于把全部元素加入小根堆
else {
for (int i = 0; i < n; ++i) {
pq.offer(new int[]{costs[i], i});
}
}
//总代价
long ans = 0;
//通过循环得到每次的最小代价
for (int i = 0; i < k; ++i) {
//取出小根堆堆顶元素,此时就为前后candidates中代价元素最小的
int[] arr = pq.poll();
int cost = arr[0], id = arr[1];
ans += cost;
//前candidates和后candidates中间还有元素的话
if (left + 1 < right) {
//取到的最小代价是左边的元素,就left往后的一个元素加入构造小根堆
if (id <= left) {
++left;
pq.offer(new int[]{costs[left], left});
} else {
//取到的最小代价是右边的元素,就right往前一个元素加入构造小根堆
--right;
pq.offer(new int[]{costs[right], right});
}
}
}
return ans;
}