**输入:** fruits = [[0,3],[6,4],[8,5]], startPos = 3, k = 2
**输出:** 0
**解释:**
最多可以移动 k = 2 步,无法到达任一有水果的地方
提示:
1 <= fruits.length <= 105
fruits[i].length == 2
0 <= startPos, positioni <= 2 * 105
对于任意 i > 0 ,positioni-1 < positioni 均成立(下标从 0 开始计数)
1 <= amounti <= 104
0 <= k <= 2 * 105
方法一:二分查找
思路与算法
由于题目中的水果位置已经是升序排列,假设此时我们知道在 x 轴上的移动区间为 [\textit{left}, \textit{right}],则可利用二分查找很快计算出区间 [\textit{left}, \textit{right}] 范围内摘掉水果的数目。题目的关键则转变为求从起点移动 k 步而实际在 x 轴上移动的最大区间范围。当然在实际移动过程中肯定优先遵循「贪心」原则,因为这样每个位置的水果只能摘取一次,因此尽可能的移动更远,实际移动方法如下:
要么一直往一个方向移动 k 步;要么先往一个方向移动 x 步,然后再反方向移动 k - x 步;
实际当 x = 0 时,则一直往一个方向移动 k 步;
根据以上分析,由于有左右两个方向,我们通过不断枚举 x,此时 x \in[0, k}{2}],即可求出其移动的区间。假设我们从起点 startPos 出发,实际有以下两种情况:
先往左移动 x 步,然后向右移动 k - x 步,此时的移动区间范围 [\textit{startPos} - x, \textit{startPos} + k - 2x];
先往右移动 x 步,然后向左移动 k - x 步,此时的移动区间范围 [\textit{startPos} + 2x - k,\textit{startPos} + x];
假设已知道当前采摘人员在 x 轴上的移动区间范围,则我们利用二分查找即可在 O(\log n) 时间复杂度内找到区间中包含的水果的数量,实际可以用前缀和进行预处理即可。
classSolution { public: intmaxTotalFruits(vector<vector<int>>& fruits, int startPos, int k){ int n = fruits.size(); vector<int> sum(n + 1); vector<int> indices(n); 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 = lower_bound(indices.begin(), indices.end(), left) - indices.begin(); int end = upper_bound(indices.begin(), indices.end(), right) - indices.begin(); ans = max(ans, sum[end] - sum[start]); /* 向右走 x 步,再向左走 k - x 步 */ y = k - 2 * x; left = startPos - y; right = startPos + x; start = lower_bound(indices.begin(), indices.end(), left) - indices.begin(); end = upper_bound(indices.begin(), indices.end(), right) - indices.begin(); ans = max(ans, sum[end] - sum[start]); } return ans; } };
publicclassSolution { publicintMaxTotalFruits(int[][] fruits, int startPos, int k) { int n = fruits.Length; int[] sum = newint[n + 1]; int[] indices = newint[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; }
publicintLowerBound(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; }
publicintUpperBound(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; } }
intlowerBound(constint *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; }
intupperBound(constint *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; }
intmaxTotalFruits(int** fruits, int fruitsSize, int* fruitsColSize, int startPos, int k) { int n = fruitsSize; int sum[n + 1]; int indices[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 = 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 = MAX(ans, sum[end] - sum[start]); } return ans; }
var maxTotalFruits = function(fruits, startPos, k) { const n = fruits.length; const sum = newArray(n + 1).fill(0); const indices = newArray(n).fill(0); sum[0] = 0; for (let i = 0; i < n; i++) { sum[i + 1] = sum[i] + fruits[i][1]; indices[i] = fruits[i][0]; } let ans = 0; for (let x = 0; x <= Math.floor(k / 2); x++) { /* 向左走 x 步,再向右走 k - x 步 */ let y = k - 2 * x; let left = startPos - x; let right = startPos + y; let start = lowerBound(indices, 0, n - 1, left); let 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; }
constlowerBound = (arr, left, right, val) => { let res = right + 1; while (left <= right) { const mid = left + Math.floor((right - left) / 2); if (arr[mid] >= val) { res = mid; right = mid - 1; } else { left = mid + 1; } } return res; }
constupperBound = (arr, left, right, val) => { let res = right + 1; while (left <= right) { const mid = left + Math.floor((right - left) / 2); if (arr[mid] > val) { res = mid; right = mid - 1; } else { left = mid + 1; } } return res; };
复杂度分析
时间复杂度:O(n + k \log n),其中 n 表示数组的长度,k 表示给定的整数 k。计算数组的前缀和需要的时间为 O(n),每次查询区间中的水果数量时需要的时间为 O(\log n),一共最多有 k 次查询,因此总的时间复杂度即为 n + k \log n。
classSolution { public: intmaxTotalFruits(vector<vector<int>>& fruits, int startPos, int k){ int left = 0; int right = 0; int n = fruits.size(); int sum = 0; int ans = 0;
auto step = [&](int left, int right) -> int { if (fruits[right][0] <= startPos) { return startPos - fruits[left][0]; } elseif (fruits[left][0] >= startPos) { return fruits[right][0] - startPos; } else { returnmin(abs(startPos - fruits[right][0]), abs(startPos - fruits[left][0])) + \ fruits[right][0] - fruits[left][0]; } }; // 每次固定住窗口右边界 while (right < n) { sum += fruits[right][1]; // 移动左边界 while (left <= right && step(left, right) > k) { sum -= fruits[left][1]; left++; } ans = max(ans, sum); right++; } return ans; } };
publicclassSolution { publicintMaxTotalFruits(int[][] fruits, int startPos, int k) { int left = 0; int right = 0; int n = fruits.Length; int sum = 0; int ans = 0; // 每次固定住窗口右边界 while (right < n) { sum += fruits[right][1]; // 移动左边界 while (left <= right && Step(fruits, startPos, left, right) > k) { sum -= fruits[left][1]; left++; } ans = Math.Max(ans, sum); right++; } return ans; }
var maxTotalFruits = function(fruits, startPos, k) { let left = 0; let right = 0; const n = fruits.length; let sum = 0; let ans = 0; // 每次固定住窗口右边界 while (right < n) { sum += fruits[right][1]; // 移动左边界 while (left <= right && step(fruits, startPos, left, right) > k) { sum -= fruits[left][1]; left++; } ans = Math.max(ans, sum); right++; } return ans; }