day117 2106. 摘水果(Java)

140 阅读3分钟

题目来源: 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)。