【2106. 摘水果】

news2024/9/23 19:28:14

来源:力扣(LeetCode)

描述:

在一个无限的 x 坐标轴上,有许多水果分布在其中某些位置。给你一个二维整数数组 fruits ,其中 fruits[i] = [positioni, amounti] 表示共有 amounti 个水果放置在 positioni 上。fruits 已经按 positioni 升序排列 ,每个 positioni 互不相同

另给你两个整数 startPosk 。最初,你位于 startPos 。从任何位置,你可以选择 向左或者向右 走。在 x 轴上每移动 一个单位 ,就记作 一步 。你总共可以走 最多 k 步。你每达到一个位置,都会摘掉全部的水果,水果也将从该位置消失(不会再生)。

返回你可以摘到水果的 最大总数

示例 1:
1

输入:fruits = [[2,8],[6,3],[8,6]], startPos = 5, k = 4
输出:9
解释:
最佳路线为:
- 向右移动到位置 6 ,摘到 3 个水果
- 向右移动到位置 8 ,摘到 6 个水果
移动 3 步,共摘到 3 + 6 = 9 个水果

示例 2:

2

输入:fruits = [[0,9],[4,1],[5,7],[6,2],[7,4],[10,9]], startPos = 5, k = 4
输出:14
解释:
可以移动最多 k = 4 步,所以无法到达位置 0 和位置 10 。
最佳路线为:
- 在初始位置 5 ,摘到 7 个水果
- 向左移动到位置 4 ,摘到 1 个水果
- 向右移动到位置 6 ,摘到 2 个水果
- 向右移动到位置 7 ,摘到 4 个水果
移动 1 + 3 = 4 步,共摘到 7 + 1 + 2 + 4 = 14 个水果

示例 3:
3

输入: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 轴上的移动区间为 [left, right],则可利用二分查找很快计算出区间 [left, right] 范围内摘掉水果的数目。题目的关键则转变为求从起点移动 k 步而实际在 x 轴上移动的最大区间范围。当然在实际移动过程中肯定优先遵循「贪心」原则,因为这样每个位置的水果只能摘取一次,因此尽可能的移动更远,实际移动方法如下:

  • 要么一直往一个方向移动 k 步;要么先往一个方向移动 x 步,然后再反方向移动 k − x 步;
  • 实际当 x = 0 时,则一直往一个方向移动 k 步;

根据以上分析,由于有左右两个方向,我们通过不断枚举 x,此时 x ∈ [0, k 2 k \over 2 2k],即可求出其移动的区间。假设我们从起点 startPos 出发,实际有以下两种情况:

  • 先往左移动 x 步,然后向右移动 k − x 步,此时的移动区间范围 [startPos − x, startPos + k − 2x];
  • 先往右移动 x 步,然后向右移动 k − x 步,此时的移动区间范围 [startPos + 2x − k, startPos + x];

假设已知道当前采摘人员在 x 轴上的移动区间范围,则我们利用二分查找即可在 O(logn) 时间复杂度内找到区间中包含的水果的数量,实际可以用前缀和进行预处理即可。

代码:

class Solution {
public:
    int maxTotalFruits(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;
    }
};

执行用时:584 ms, 在所有 C++ 提交中击败了37.93%的用户
内存消耗:120.9 MB, 在所有 C++ 提交中击败了53.45%的用户
复杂度分析
时间复杂度:O(n + klogn),其中 n 表示数组的长度,k 表示给定的整数 k。计算数组的前缀和需要的时间为 O(n),每次查询区间中的水果数量时需要的时间为 O(logn),一共最多有 k 次查询,因此总的时间复杂度即为 n + klogn。
空间复杂度:O(n),其中 n 表示数组的长度。计算并存储数组的前缀和,需要的空间为 O(n)。

方法二:滑动窗口

思路与算法

我们可以换个思路来思考该问题,假设已知区间 [left, right],现在从起点 startPos 出发,至少需要走多少步才能遍历该区间,实际我们可以看到分为以下三种情况:

  • 当 startPos > right 时,即区间在 startPos 的左边,此时应该从起点开始一直向左移动,直到 left 为止,此时至少需要移动 startPos − left 步;
  • 当 startPos < left 时,即区间在 startPos 的右边,此时应该从起点开始一直向右移动,直到 right 为止,此时至少需要移动 right − startPos 步;
  • 当 left ≤ startPos ≤ right 时,即 startPos 刚好在区间范围内,此时有两种选择:
    • 从起点开始一直向左移动,直到 left 为止,然后再向右移动到 right,此时需要移动 startPos − left + right − left 步;
    • 从起点开始一直向右移动,直到 right 为止,然后再向左移动到 left,此时最少需要移动 right − startPos + right − left 步;
    • 根据两种情形,最少需要移动 right − left + min(∣right − startPos∣, ∣startPos − left∣) 步;
  • 当然上述所有的情形都可以合并为一个计算公式,即实际最少需要移动 right − left + min(∣right − startPos∣, ∣startPos − left∣) 步,才能覆盖区间 [left, right],如下图所示:
    4

我们设函数 step(left, right) 表示从起点 startPos 出发可以覆盖区间 [left, right] 的最少移动步数,此时 step(left,right) = right − left + min(∣right − startPos∣, ∣startPos − left∣)。当固定 right 时,此时减少 left,可以观察到:

  • 当 left < startPos 时,step(left − 1, right) < step(left, right);
  • 当 left ≥ startPos 时,step(left − 1, right) = step(left, right);

综上可以得到结论:

step(left − 1, right) ≤ step(left, right)

即随着 left 的减小,step(left, right) 可能会减小,但一定不会继续增大,利用这个特性我们即可利用滑动窗口来遍历所有符合要求的最大区间,然后找到区间内的覆盖水果的最大值即可,实际计算过程如下:
  • 初始时 left = 0, right = 0,每次 right 向右移动一步;
  • 计算当前区间 [left, right] 需要的移动步数 step,假设 step > k,则我们移动左起点 left,直达满足 step < k, left ≤ right,即可求出移动步数小于等于 k 且以 right 为终点的最长区间,计算出改区间覆盖的水果数目即可;
  • 依次按照上述方式移动直到 right 移动到终点为止。

代码:

class Solution {
public:
    int maxTotalFruits(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];
            } else if (fruits[left][0] >= startPos) {
                return fruits[right][0] - startPos;
            } else {
                return min(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;
    }
};

执行用时:316 ms, 在所有 C++ 提交中击败了81.03%的用户
内存消耗:117.3 MB, 在所有 C++ 提交中击败了75.86%的用户
复杂度分析
时间复杂度:O(n),其中 n 表示数组的长度。每次固定窗口的右侧,然后尝试移动左侧窗口,右侧端点最多移动 n 次,左侧端点最多移动 n 次,因此时间复杂度为 O(2n) = O(n)。
空间复杂度:O(1)。
author:LeetCode-Solution

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/498822.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android平台播放透明视频

Android平台播放透明视频 思路 设计一种特殊的视频&#xff0c;它的一半内容存储alpha信息&#xff0c;另一半内容存储rgb信息&#xff0c;接着通过OpenGL获取每个像素点的alpha值和rgb值进行混合&#xff0c;最后出来的画面就是带有透明效果的视频了。 可以上下的分&#xf…

【疯狂造轮子-iOS】JSON转Model系列之二

1. 前言 上一篇《【疯狂造轮子-iOS】JSON转Model系列之一》实现了一个简陋的JSON转Model的库&#xff0c;不过还存在很多问题。下面我会尝试一个个去解决。 2. 存在问题及解决思路 2.1 没有考虑JSON数据并不一定是NSDictionary类型 有时候JSON并不一定是NSDictionary类型&…

【医学影像数据处理】2D/3D patch的crop和merge操作汇总

在做3D分割任务中&#xff0c;多数的方法多采用整体缩放&#xff0c;或裁剪成一个个小的patch操作&#xff0c;这样做的一个主要原因是内存问题。 相较于整体缩放&#xff0c;采用裁剪成patch的方法&#xff0c;对于小目标会更加的鲁棒&#xff0c;这也是大多数3D分割任务中常…

Leetcode448. 找到所有数组中消失的数字

Every day a leetcode 题目来源&#xff1a;448. 找到所有数组中消失的数字 解法1&#xff1a;STL set set 是一个集合类型的容器&#xff0c;里面的元素具有唯一性&#xff0c;并且所有元素都会根据元素的键值自动被排序&#xff0c;以红黑树为底层数据结构。 我们使用集合…

git上传大大大文件项目好折磨人

本来想把unity项目的源码上传上gitee啊&#xff0c;但是那个项目有1个多G&#xff0c;还是个半成品&#xff0c;要是写完&#xff0c;都不知道行不行 正常的上传 所用到的命令&#xff1a; 1、 git init 初始化&#xff0c;创建本地仓库 2、 git add . 添加到本地仓库 3、 git…

【C++】打开C++大门,踏入C++世界

文章目录 ☑️前言一. 浅看【C】关键字二. 命名空间1. 命名空间的定义&#x1f44c;2. 命名空间的使用&#x1f44c; 三. 【C】输入输出(IO)四. 缺省参数1. 缺省参数的概念&#x1f3c6;2. 缺省参数的分类&#x1f3c6; 五. 函数重载1. 函数重载的概念✌️2. 【C】支持函数重载…

【计算机专业漫谈】【计算机系统基础学习笔记】W2-2-2 模运算系统和补码表示

利用空档期时间学习一下计算机系统基础&#xff0c;以前对这些知识只停留在应试层面&#xff0c;今天终于能详细理解一下了。参考课程为南京大学袁春风老师的计算机系统基础MOOC&#xff0c;参考书籍也是袁老师的教材&#xff0c;这是我的听课自查资料整理后的笔记 补码表示法…

实验9 分类问题

1. 实验目的 ①掌握逻辑回归的基本原理&#xff0c;实现分类器&#xff0c;完成多分类任务&#xff1b; ②掌握逻辑回归中的平方损失函数、交叉熵损失函数以及平均交叉熵损失函数。 2. 实验内容 ①能够使用TensorFlow计算Sigmoid函数、准确率、交叉熵损失函数等&#xff0c;…

GEE:基于变异系数法的遥感环境风险评估指数(RSEI)计算

作者:CSDN @ _养乐多_ 本文记录了基于变异系数法计算 Risk-Screening Environmental Indicators (RSEI) 的方法和代码。使用 变异系数法计算权重来代替PCA方法计算权重,根据权重计算综合指标。本文也记录了使用landsat-8数据计算LST、NDVI、NDBSI、WET四个指标的代码。 结果…

密码学:其他常见密码学应用.

密码学&#xff1a;其他常见密码学应用. 密码学是研究编制密码和破译密码的技术科学。研究密码变化的客观规律&#xff0c;应用于编制密码以保守通信秘密的&#xff0c;称为编码学&#xff1b;应用于破译密码以获取通信情报的&#xff0c;称为破译学&#xff0c;总称密码学. 目…

二叉树基础概念详解

文章目录 前言1. 树的基本概念2. 二叉树的基本概念3. 特殊二叉树&#x1f351; 满二叉树&#x1f351; 完全二叉树&#x1f351; 斜树 4. 二叉树的性质&#x1f351; 性质一&#x1f351; 性质二&#x1f351; 性质三&#x1f351; 性质四&#x1f351; 性质五&#x1f351; 性…

华为EC6108V9E/EC6108V9I_rk3228_安卓4.4.4_通刷_卡刷固件包

华为EC6108V9E&#xff0f;EC6108V9I_rk3228_安卓4.4.4_通刷_卡刷固件包-内有教程 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简内置的…

【Java】线程池--ThreadPoolExecutor底层原理源码的理解

文章目录 一、根据代码查看jdk提供的3种线程池创建&#xff1a;二、3种方式源码分析1、Executors.newCachedThreadPool()2、Executors.newFixedThreadPool(10)3、Executors.newSingleThreadExecutor() 三、自定义方式执行提交优先级执行优先级 一、根据代码查看jdk提供的3种线程…

第2章Elasticsearch入门

1.正排索引和倒排索引 正排索引: 正排索引&#xff08;Forward Index&#xff09;是一种用于搜索引擎和文本检索系统的索引结构&#xff0c;它将文档中的每个单词都映射到该单词在文档中出现的位置。正排索引可以帮助快速定位、检索和渲染文档内容&#xff0c;但它需要消耗大…

AI 工具合辑盘点(十一)持续更新 之 AI 学术研究工具

许多学生和研究人员已经在利用人工智能进行研究。它可以让你更容易地了解最新研究成果&#xff0c;并帮助你组织和正确引用你最喜爱的研究论文。 从生成长篇研究论文摘要到通知你领域内的新趋势&#xff0c;研究中的AI工具节省了大量时间和精力。如果你在学术界&#xff0c;那…

数据库系统

目录 第三章、数据库系统1、数据库模式1.1、三级模式--两级映射1.2、数据库设计过程 2、ER模型3、关系代数与元组演算4、规范化理论4.1、函数依赖4.2、价值与用途4.3、键4.4、范式4.5、模式分解 5、并发控制6、数据库完整性约束7、数据备份8、数控故障与恢复9、数据仓库与数据挖…

网络安全合规-数据安全风险评估

一、法律依据&#xff1a; 依据《数据安全法》第三十条的规定&#xff0c;重要数据的处理者应当按照规定对其数据处理活动定期开展风险评估&#xff0c;并向有关主管部门报送风险评估报告。 依据《网络数据安全管理条例》&#xff08;征求意见稿&#xff09; 第三十二条规定&am…

实时通信的服务器推送机制 EventSource(SSE) 简介,附 go 实现示例

简介 不知道大家有没有见过 Content-Type:text/event-stream 的请求头&#xff0c;这是 HTML5 中的 EventSource 是一项强大的 API&#xff0c;通过服务器推送实现实时通信。 与 WebSocket 相比&#xff0c;EventSource 提供了一种简单而可靠的单向通信机制&#xff08;服务器…

《Linux 内核设计与实现》03. 进程管理

文章目录 进程描述符及任务结构分配进程描述符进程描述符的存放进程状态设置当前进程状态进程上下文进程家族树 进程创建线程在 Linux 中的实现创建线程内核线程 进程终结删除进程描述符孤儿进程 进程描述符及任务结构 内核把进程存放在任务队列&#xff08;task list&#xf…

MySQL高级--锁

一、锁 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源&#xff08;CPU、RAM、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题…