题目来源: 2106. 摘水果
题目描述:
-
描述: 在一个无限的 x 坐标轴上,有许多水果分布在其中某些位置。给你一个二维整数数组 fruits ,其中 fruits[i] = [positioni, amounti] 表示共有 amounti 个水果放置在 positioni 上。fruits 已经按 positioni 升序排列 ,每个 positioni 互不相同 。 另给你两个整数 startPos 和 k 。最初,你位于 startPos 。从任何位置,你可以选择 向左或者向右 走。在 x 轴上每移动 一个单位 ,就记作 一步 。你总共可以走 最多 k 步。你每达到一个位置,都会摘掉全部的水果,水果也将从该位置消失(不会再生)。 返回你可以摘到水果的 最大总数 。
-
示例:
示例1:
输入:fruits = [[2,8],[6,3],[8,6]], startPos = 5, k = 4
输出:9
解释:最佳路线为:
- 向右移动到位置 6 ,摘到 3 个水果
- 向右移动到位置 8 ,摘到 6 个水果 移动 3 步,共摘到 3 + 6 = 9 个水果
示例2:
输入:nums = [0,0,0], target = 1
输出:0
思路
思路1 由于题目中的水果位置已经是升序排列,假设此时我们知道在x 轴上的移动区间为[left,right],则可利用二分查找很快计算出区间[left,right] 范围内摘掉水果的数目。题目的关键则转变为求从起点移动k 步而实际在x 轴上移动的最大区间范围。当然在实际移动过程中肯定优先遵循「贪心」原则,因为这样每个位置的水果只能摘取一次,因此尽可能的移动更远,实际移动方法如下:
- 要么一直往一个方向移动k 步;要么先往一个方向移动x 步,然后再反方向移动k−x 步;
- 实际当x=0 时,则一直往一个方向移动k 步;
根据以上分析,由于有左右两个方向,我们通过不断枚举x,此时x∈[0,k/2 ],即可求出其移动的区间。假设我们从起点startPos 出发,实际有以下两种情况:
- 先往左移动x 步,然后向右移动k−x 步,此时的移动区间范围[startPos−x,startPos+k−2x];
- 先往右移动x 步,然后向右移动k−x 步,此时的移动区间范围[startPos+2x−k,startPos+x];
假设已知道当前采摘人员在x 轴上的移动区间范围,则我们利用二分查找即可在O(logn) 时间复杂度内找到区间中包含的水果的数量,实际可以用前缀和进行预处理即可。
具体实现1
class Solution {
public int maxTotalFruits(int[][] fruits, int startPos, int k) {
int n = fruits.length;
int[] sum = new int[n + 1];
int[] indices = new int[n];
sum[0] = 0;
for (int i = 0; i < n; i++) {
sum[i + 1] = sum[i] + fruits[i][1];
indices[i] = fruits[i][0];
}
int ans = 0;
for (int x = 0; x <= k / 2; x++) {
/* 向左走 x 步,再向右走 k - x 步 */
int y = k - 2 * x;
int left = startPos - x;
int right = startPos + y;
int start = lowerBound(indices, 0, n - 1, left);
int end = upperBound(indices, 0, n - 1, right);
ans = Math.max(ans, sum[end] - sum[start]);
/* 向右走 x 步,再向左走 k - x 步 */
y = k - 2 * x;
left = startPos - y;
right = startPos + x;
start = lowerBound(indices, 0, n - 1, left);
end = upperBound(indices, 0, n - 1, right);
ans = Math.max(ans, sum[end] - sum[start]);
}
return ans;
}
public int lowerBound(int[] arr, int left, int right, int val) {
int res = right + 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] >= val) {
res = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return res;
}
public int upperBound(int[] arr, int left, int right, int val) {
int res = right + 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] > val) {
res = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return res;
}
}
复杂度分析1:
-
时间复杂度:O(n+klogn),其中n 表示数组的长度,k 表示给定的整数k。计算数组的前缀和需要的时间为 O(n),每次查询区间中的水果数量时需要的时间为 O(logn),一共最多有k 次查询,因此总的时间复杂度即n+klogn。
-
空间复杂度:O(n),其中n 表示数组的长度。计算并存储数组的前缀和,需要的空间为O(n)。