传送带上的包裹必须在 D 天内从一个港口运送到另一个港口。
传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
返回能在 D 天内将传送带上的所有包裹送达的船的最低运载能力。
原题地址:在D天内送达包裹的能力
- 我的超时题解
class Solution {
public:
int shipWithinDays(vector<int>& weights, int D) {
int _chazhi = weights.size() - D;
int _maxwt = 0;
int _capacity = 0;
int _i = 0;
int _tempsum = 0;
int _manydays = 0;
for (int i = 0; i < weights.size(); ++i) {
if (_maxwt < weights[i]){
_maxwt = weights[i];
}
}
_capacity = _maxwt;
while(1){
while (_i < weights.size()){
if ((weights[_i]+_tempsum) <= _capacity){
_tempsum+=weights[_i];
_i++;
if(_i == weights.size()){
_manydays++;
}
}
else{
// _i++;
_manydays++;
_tempsum=0;
continue;
}
}
if(_manydays <= D){
break;
}
else{
_capacity++;
_tempsum = 0;
_i = 0;
_manydays = 0;
}
}//无限循环
return _capacity;
}
};
暴力法,我没了,暴力不给过。
- 大佬的DP做法
class Solution {
public:
int shipWithinDays(vector<int>& weights, int D) {
// 确定二分查找左右边界
int left = *max_element(weights.begin(), weights.end()), right = accumulate(weights.begin(), weights.end(), 0);
while (left < right) {
int mid = (left + right) / 2;
// need 为需要运送的天数
// cur 为当前这一天已经运送的包裹重量之和
int need = 1, cur = 0;
for (int weight: weights) {
if (cur + weight > mid) {
++need;
cur = 0;
}
cur += weight;
}
if (need <= D) {
right = mid;
}
else {
left = mid + 1;
}
}
return left;
}
};
//作者:LeetCode-Solution
仔细思考并看题解之后理解了大佬的部分思路。
首先,已知运货能力最小是最重货物,因为若达不到最重货物,货物都运不完。
其次,可以以二分法为基础,以所有货物重量之和进行二分,这样逼近正确答案的速度更快,是log复杂度。不像我的暴力解法,是通过线性检索。
再者,因为最小运输能力是最重货物,我们绝对可以将所有货物运走,只是时间的问题。即利用这一段代码来求解目前所需天数:
int mid = (left + right) / 2;//取mid为当前的运输能力
// need 为需要运送的天数
// cur 为当前这一天已经运送的包裹重量之和
for (int weight: weights)
{
if (cur + weight > mid) {
++need;
cur = 0;
}
cur += weight;
}
因为mid是当前运输能力,在cur + weight > mid
的时候天数加 1 ,而初始化 cur(当前运输量)。
最后只需要判断 need(所需天数) 与D的关系,比目标天数D少的话,就将右边界左移,因为我们要求尽量接近D,运输能力不是越强越好。 比目标天数D多的话,就将左边界右移(即提高运输能力,减少天数,因为D天正好运完是底线)。
最后返回left,老模板了。
二分方法第一次在这实际应用,这里顺便加一下二分法模板。
—————————————————————————————————————————————————————
//二分查找
int lower_bound(int A[], int left, int right, int x){//还是利用角标作用排序后的数组
int mid;//mid作为left和right的中点
while (left < right){// 对[left,right]来说,left==right 意味着找到唯一位置
mid = (left + right)/2;//取到中点
if (A[mid] >= x){//------------------>数组中对应位置的数字比目标数字大,肯定x就在A[mid]的左方
right = mid; //因此有right = mid,即缩小区间
} else{
left = mid+1;
}
}
if( A[left] ==x ){
return left;
}
else{
return -10086;
}//原算法没考虑找不到吧
return left;
}