跳转至

3506. 查找消除细菌菌株所需时间 II 🔒

题目描述

给定一个整数数组 timeReq 和一个整数 splitTime

在人体微观世界中,免疫系统面临着一项非凡的挑战:对抗快速繁殖的细菌群落,这对身体的生存构成威胁。

最初,只部署一个 白细胞WBC)来消除细菌。然而,单独的白细胞很快意识到它无法跟上细菌的生长速度。

WBC制定了一种巧妙的策略来对抗细菌:

  • i 个细菌菌株需要 timeReq[i] 个时间单位来被消除。
  • 单个白细胞只能消除 一种 细菌菌株。之后,白细胞耗尽,无法执行任何其他任务。
  • 一个白细胞可以将自身分裂为两个白细胞,但这需要 splitTime 单位时间。一旦分裂,两个白细胞就可以 并行 消灭细菌。
  • 仅有一个白细胞可以针对一个单一细菌菌株工作。多个白细胞不能同时攻击一个菌株。

您必须确定消除所有细菌菌株所需的 最短 时间。

注意,细菌菌株可以按任何顺序消除。

 

示例 1:

输入:timeReq = [10,4,5], splitTime = 2

输出:12

解释:

消除过程如下:

  • 最初,有一个白细胞。经过 2 个时间单位后,白细胞分裂成 2 个白细胞。
  • 其中一个白细胞在 t = 2 + 10 = 12 时间内消除菌株 0。另一个白细胞使用 2 个单位时间再次分裂。
  • 2 个新的白细胞消灭细菌的时间是 t = 2 + 2 + 4 和 t = 2 + 2 + 5

示例 2:

输入:timeReq = [10,4], splitTime = 5

输出:5

解释:

消除过程如下:

  • 最初,有一个白细胞。经过 5 个时间单位后,白细胞分裂成 2 个白细胞。
  • 2 个新的白细胞消灭细菌的时间是 t = 5 + 10 和 t = 5 + 4

 

提示:

  • 2 <= timeReq.length <= 105
  • 1 <= timeReq[i] <= 109
  • 1 <= splitTime <= 109

解法

方法一:贪心 + 优先队列(小根堆)

先考虑只有一种细菌的情况,此时不需要分裂白细胞,直接让他去消灭细菌,时间花费为 \(\textit{timeSeq}[0]\)

如果有两种细菌,此时需要把白细胞分裂为两种,然后让它们分别去消灭细菌,时间花费为 \(\textit{splitTime} + \max(\textit{timeSeq}[0], \textit{timeSeq}[1])\)

如果有超过两种细菌,此时每一步都需要考虑将几个白细胞进行分裂,正向思维不好处理。

我们不妨采用逆向思维,不分裂白细胞,而是将细菌进行合并。我们选取任意两种细菌 \(i\), \(j\) 进行合并,合并成一种新的细菌的时间为 \(\textit{splitTime} + \max(\textit{timeSeq}[i], \textit{timeSeq}[j])\)

为了让耗时长的细菌尽可能少参与到合并中,我们可以每次贪心地选取耗时最小的两种细菌进行合并。因此,我们可以维护一个小根堆,每次取出最小的两种细菌进行合并,直到只剩下一种细菌。最后剩下的这个细菌的消灭时间就是答案。

时间复杂度 \(O(n \times \log n)\),空间复杂度 \(O(n)\)。其中 \(n\) 为细菌的数量。

1
2
3
4
5
6
7
class Solution:
    def minEliminationTime(self, timeReq: List[int], splitTime: int) -> int:
        heapify(timeReq)
        while len(timeReq) > 1:
            heappop(timeReq)
            heappush(timeReq, heappop(timeReq) + splitTime)
        return timeReq[0]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Solution {
    public long minEliminationTime(int[] timeReq, int splitTime) {
        PriorityQueue<Long> q = new PriorityQueue<>();
        for (int x : timeReq) {
            q.offer((long) x);
        }
        while (q.size() > 1) {
            q.poll();
            q.offer(q.poll() + splitTime);
        }
        return q.poll();
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Solution {
public:
    long long minEliminationTime(vector<int>& timeReq, int splitTime) {
        using ll = long long;
        priority_queue<ll, vector<ll>, greater<ll>> pq;
        for (int v : timeReq) {
            pq.push(v);
        }
        while (pq.size() > 1) {
            pq.pop();
            ll x = pq.top();
            pq.pop();
            pq.push(x + splitTime);
        }
        return pq.top();
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func minEliminationTime(timeReq []int, splitTime int) int64 {
    pq := hp{}
    for _, v := range timeReq {
        heap.Push(&pq, v)
    }
    for pq.Len() > 1 {
        heap.Pop(&pq)
        heap.Push(&pq, heap.Pop(&pq).(int)+splitTime)
    }
    return int64(pq.IntSlice[0])
}

type hp struct{ sort.IntSlice }

func (h *hp) Push(v any) { h.IntSlice = append(h.IntSlice, v.(int)) }
func (h *hp) Pop() any {
    a := h.IntSlice
    v := a[len(a)-1]
    h.IntSlice = a[:len(a)-1]
    return v
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function minEliminationTime(timeReq: number[], splitTime: number): number {
    const pq = new MinPriorityQueue();
    for (const b of timeReq) {
        pq.enqueue(b);
    }
    while (pq.size() > 1) {
        pq.dequeue()!;
        pq.enqueue(pq.dequeue()! + splitTime);
    }
    return pq.dequeue()!;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::cmp::Reverse;
use std::collections::BinaryHeap;

impl Solution {
    pub fn min_elimination_time(time_req: Vec<i32>, split_time: i32) -> i64 {
        let mut pq = BinaryHeap::new();
        for x in time_req {
            pq.push(Reverse(x as i64));
        }
        while pq.len() > 1 {
            pq.pop();
            let merged = pq.pop().unwrap().0 + split_time as i64;
            pq.push(Reverse(merged));
        }
        pq.pop().unwrap().0
    }
}

评论