原贴地址:http://blog.leanote.com/post/dawnmagnet/1011
题目
传送带上的包裹必须在 D 天内从一个港口运送到另一个港口。
传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
返回能在 D 天内将传送带上的所有包裹送达的船的最低运载能力。
提示:
1 <= D <= weights.length <= 50000
1 <= weights[i] <= 500
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/capacity-to-ship-packages-within-d-days
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路分析
这道题需要一个整体观来解答,我们知道,这个答案和题目是一个单向的关系。什么是单向的关系呢,就是我们用答案(假设我们知道了最低运载能力)可以很方便地求出题目中的条件(即很轻松,可以在O(n)内求出最小的D天)。
但是从D天倒推回最低运载能力就有点困难了。
既然这个关系是单向的,那么我们就可以通过枚举最低运载能力来反推是否可以在D天内完成运输。
但是如此一来,复杂度就不容忽视了,高达
O
(
n
∗
∑
i
=
0
n
w
e
i
g
h
t
s
[
i
]
)
O(n*\sum_{i=0}^{n}{weights[i]})
O(n∗∑i=0nweights[i]),换算成最大的数字就是5w*5w*500,实在是一个天文数字,可是我们只有这一种看起来还比较合理的方法。
在这里重要声明一点,就是在做题目的时候算法的正确性是非常关键的。在做一个算法的时候不应该优先考虑复杂度,而应该先考虑获得一个正确的思路。后续再考虑如何优化。
接下来就属于优化的范围。枚举可以用什么来优化呢?非常容易联想到就是二分。
但是二分是需要条件的。我们之前经常使用的二分是用来在有序数组中查找一个数字存在的位置。那么对于我们来说,二分的条件就是有序数组。但是这次我们的这个题目,和二分有什么联系呢?
这里我们就要引入一个“广义二分”的概念。对于一群数字来说,只要这些数字有一个分割点,位于该分割点前的数字都满足一个条件,位于分割点后方的数字都不满足该条件,那么我们就可以对这些数字进行“广义二分”。其实我们只要细想就可以发现,我们平时所用的二分查找正是广义二分的一个特例。假设我们要查找的数字是key,数组有序的条件也可以这样表述:位于key前方的数字都满足“大于等于key”条件,而位于key后方的数组都不满足该条件。
而对于这道题目来说,条件也是满足的,即在最低运载能力之上的,都会满足运载天数小于等于D,而在运载能力之下的,在D天之内都运不完。
那么对我们而言,这道题目就可以使用“广义二分”来完成。也就是说,我们对运载能力进行二分,对一个输入的运载能力,应该获得一个布尔值-是否能在D天之内运输完成,作为二分的依据。这个获取是较为简单的,O(n)时间甚至O(lgn)就可以完成,但我们为了便于展示主要的二分算法,这里就做简化处理,使用传统的O(n)模拟算法。
Rust代码
impl Solution {
pub fn ship_within_days(weights: Vec<i32>, d: i32) -> i32 {
let mut right = weights[0] + 1;
let mut left = weights[0];
let n = weights.len();
for i in 1..n {
right += weights[i];
left = left.max(weights[i]);
}
while left < right {
let cap = (left + right) / 2;
let mut pointer = 0;
for i in 0..d {
let mut caps = cap;
while pointer < n && caps > weights[pointer] {
caps -= weights[pointer];
pointer += 1;
}
}
if pointer < n {
left = cap + 1;
} else {
right = cap;
}
}
left - 1
}
}
C++代码
class Solution {
public:
int shipWithinDays(vector<int>& weights, int D) {
int right = weights[0] + 1, left = weights[0], n = weights.size();
for (int i = 1; i < n; ++i) {
right += weights[i];
left = max(left, weights[i]);
}
while (left < right) {
int cap = (left + right) / 2, pointer = 0;
for (int i = 0; i < D; ++i) {
int capt = cap;
while (pointer < n && capt > weights[pointer]) {
capt -= weights[pointer];
pointer++;
}
}
if (pointer < n) left = cap + 1;
else right = cap;
}
return left - 1;
}
};