You have $n$ robots. You are given two 0-indexed integer arrays, chargeTimes and runningCosts , both of length $n$. The i^{th} robot costs chargeTimes[i] units to charge and costs runningCosts[i] units to run. You are also given an integer budget .
The total cost of running $k$ chosen robots is equal to max(chargeTimes) + k * sum(runningCosts) , where max(chargeTimes) is the largest charge cost among the $k$ robots and sum(runningCosts) is the sum of running costs among the $k$ robots.
Return the maximum number of consecutive robots you can run such that the total cost does not exceed budget .
Input: chargeTimes = [3,6,1,3,4], runningCosts = [2,1,3,4,5], budget = 25 Output: 3 Explanation: It is possible to run all individual and consecutive pairs of robots within budget. To obtain answer 3, consider the first 3 robots. The total cost will be max(3,6,1) + 3 * sum(2,1,3) = 6 + 3 * 6 = 24 which is less than 25. It can be shown that it is not possible to run more than 3 consecutive robots within budget, so we return 3.
Input: chargeTimes = [11,12,19], runningCosts = [10,8,7], budget = 19 Output: 0 Explanation: No robot can be run that does not exceed the budget, so we return 0.
$chargeTimes.length == runningCosts.length == n$ $1 \leq n \leq 5 \times {10}^{4}$ $1 \leq chargeTimes[i], runningCosts[i] \leq {10}^{5}$ $1 \leq budget \leq {10}^{15}$
可以发现答案具有二段性,因此可以二分答案。假设最优解为长度为$ans$的区间,把这个区间右端点的元素去掉,可以发现区间最大值不会变大,而区间和变小(每个元素都是正整数),因此总代价减小。即如果最优解为$ans$,那么长度小于$ans$的区间也一定满足条件。因此具有二段性。
然后在二分确定了区间长度后,每个区间的大小都是固定的,check函数就需要遍历所有长度相同的区间,并求每个区间的最大值和区间和,区间最值可以用单调队列来维护,区间和可以用一个变量来维护。
AC代码如下,时间复杂度为$O(nlogn)$:
1 class Solution { 2 public: 3 int n; 4 long long m; 5 6 bool check(int len, vector<int>& chargeTimes, vector<int>& runningCosts) { 7 deque<int> q; 8 long long s = 0; 9 for (int i = 0; i < n; i++) { 10 // 维护区间最值 11 if (!q.empty() && i - q.front() + 1 > len) q.pop_front(); 12 while (!q.empty() && chargeTimes[q.back()] <= chargeTimes[i]) { 13 q.pop_back(); 14 } 15 q.push_back(i); 16 17 // 维护区间和 18 s += runningCosts[i]; 19 if (i - len >= 0) s -= runningCosts[i - len]; 20 21 if (i - len + 1 >= 0 && chargeTimes[q.front()] + len * s <= m) return true; 22 } 23 24 return false; 25 } 26 27 int maximumRobots(vector<int>& chargeTimes, vector<int>& runningCosts, long long budget) { 28 n = chargeTimes.size(); 29 m = budget; 30 31 int l = 0, r = n; 32 while (l < r) { 33 int mid = l + r + 1 >> 1; 34 if (check(mid, chargeTimes, runningCosts)) l = mid; 35 else r = mid - 1; 36 } 37 38 return l; 39 } 40 };
还可以用双指针。定义指针$j$为当指针$i$固定后,满足条件的最左边的位置。当$i$往右边移动,$j$也应该往右边移动。假设$i$往右移动为$i'$,对应的最左边的位置为$j'$,且$j'$在$j$的左边,由于$j' \sim i'$这个区间满足要求,而$j' \sim i$也满足要求(前面二段性的证明),因此就与$j$为指针$i$最左边的位置矛盾了。
AC代码如下,时间复杂度为$O(n)$:
1 class Solution { 2 public: 3 int maximumRobots(vector<int>& chargeTimes, vector<int>& runningCosts, long long budget) { 4 int n = chargeTimes.size(); 5 long long m = budget; 6 7 deque<int> q; 8 long long s = 0; 9 int ret = 0; 10 for (int i = 0, j = 0; i < n; i++) { 11 s += runningCosts[i]; 12 while (!q.empty() && chargeTimes[q.back()] <= chargeTimes[i]) { 13 q.pop_back(); 14 } 15 q.push_back(i); 16 17 while (!q.empty() && chargeTimes[q.front()] + (i - j + 1) * s > m) { 18 s -= runningCosts[j]; 19 if (q.front() == j) q.pop_front(); 20 j++; 21 } 22 23 ret = max(ret, i - j + 1); 24 } 25 26 return ret; 27 } 28 };
最后扩展一下,题目要求的子序列是连续的,如果子序列可以不连续那应该怎么做呢?
也是二分答案。先对所有机器人按照取最大值的那项 chargeTimes 按照从小到大的顺序排序,根据二分的值选出对应个数的机器人,假设是$len$个,枚举最大值选哪个,假设当前枚举到第$i$个,然后剩下的$len-1$个机器人一定是从排序后的第$i$个位置之前的机器人中选(如果从第$i$个位置后面选的话,那么最大值就不是第$i$个机器人了)。为了满足条件,应该从前面的机器人中选择$len-1$个最小的 runningCosts (总和应该尽可能小),这个可以用堆来维护。每次往堆插入一个元素,如果发现堆中的元素个数大于$k$,那么就把堆中最大的元素弹出。
代码如下,时间复杂度为$O(n \cdot {log}^{2} n)$
1 class Solution { 2 public: 3 int n; 4 long long m; 5 vector<int> idx; 6 7 bool check(int len, vector<int>& chargeTimes, vector<int>& runningCosts) { 8 priority_queue<int> pq; 9 long long s = 0; 10 for (int i = 0; i < n; i++) { 11 if (pq.size() == len) { 12 s -= pq.top(); 13 pq.pop(); 14 } 15 pq.push(runningCosts[idx[i]]); 16 s += runningCosts[idx[i]]; 17 if (pq.size() == len && chargeTimes[idx[i]] + len * s <= m) return true; 18 } 19 20 return false; 21 } 22 23 int maximumRobots(vector<int>& chargeTimes, vector<int>& runningCosts, long long budget) { 24 n = chargeTimes.size(); 25 m = budget; 26 27 for (int i = 0; i < n; i++) { 28 idx.push_back(i); 29 } 30 sort(idx.begin(), idx.end(), [&](int a, int b) { 31 return chargeTimes[a] < chargeTimes[b]; 32 }); 33 34 int l = 0, r = n; 35 while (l < r) { 36 int mid = l + r + 1 >> 1; 37 if (check(mid, chargeTimes, runningCosts)) l = mid; 38 else r = mid - 1; 39 } 40 41 return l; 42 } 43 };
力扣第86场双周赛:https://www.bilibili.com/video/BV11G41157DN