当前位置: 首页 > news >正文

2106. 摘水果,梳理思路

文章目录

    • 题目概要
    • java 解法
    • 详解

在这里插入图片描述

题目概要

在一个无限的 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:
在这里插入图片描述

输入: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:

在这里插入图片描述

输入:fruits = [[0,3],[6,4],[8,5]], startPos = 3, k = 2
输出:0
解释:
最多可以移动 k = 2 步,无法到达任一有水果的地方

提示:
1<=fruits.length<=105fruits[i].length==20<=startPos,positioni<=2∗105对于任意i>0,positioni−1<positioni均成立(下标从0开始计数)1<=amounti<=1040<=k<=2∗1051 <= fruits.length <= 10^5 \\ fruits[i].length == 2\\ 0 <= startPos, positioni <= 2 * 10^5 \\ 对于任意 i > 0 ,positioni-1 < positioni 均成立(下标从 0 开始计数)\\ 1 <= amounti <= 10^4 \\ 0 <= k <= 2 * 10^5 1<=fruits.length<=105fruits[i].length==20<=startPos,positioni<=2105对于任意i>0positioni1<positioni均成立(下标从0开始计数)1<=amounti<=1040<=k<=2105

java 解法

public int maxTotalFruits(int[][] fruits, int startPos, int k) {int n = fruits.length;// 提取位置和数量,方便操作int[] positions = new int[n];int[] amounts = new int[n];for (int i = 0; i < n; i++) {positions[i] = fruits[i][0];  // 位置amounts[i] = fruits[i][1];    // 水果数量}// 前缀和数组:prefix[i] 表示前 i 个位置的水果总数int[] prefix = new int[n + 1];for (int i = 0; i < n; i++) {prefix[i + 1] = prefix[i] + amounts[i];}int maxFruits = 0;  // 记录能摘到的最大水果数int left = 0;       // 滑动窗口的左指针// 滑动窗口:右指针 right 从 0 到 n-1for (int right = 0; right < n; right++) {// 当前尝试的区间是 [left, right]int L = positions[left];      // 区间最左位置int R = positions[right];     // 区间最右位置// 计算从 startPos 出发,走完 [L, R] 所需的最少步数int steps = calculateSteps(startPos, L, R);// 如果步数超过了 k,说明走太远了,需要缩小窗口(移动 left)while (left <= right && steps > k) {left++;  // 缩小左边界if (left > right) break;L = positions[left];R = positions[right];steps = calculateSteps(startPos, L, R);}// 如果当前窗口有效,更新最大水果数if (left <= right) {int total = prefix[right + 1] - prefix[left];  // [left, right] 的水果总数maxFruits = Math.max(maxFruits, total);}}return maxFruits;
}// 计算从 startPos 出发,走完区间 [L, R] 所需的最少步数
private int calculateSteps(int startPos, int L, int R) {if (R <= startPos) {// 所有水果都在起点或左边:只向左走return startPos - L;} else if (L >= startPos) {// 所有水果都在起点或右边:只向右走return R - startPos;} else {// 水果分布在起点两侧// 方案1:先向左走到 L,再向右走到 Rint goLeftFirst = (startPos - L) * 2 + (R - startPos);// 方案2:先向右走到 R,再向左走到 Lint goRightFirst = (R - startPos) * 2 + (startPos - L);// 返回两种方案中步数更少的那个return Math.min(goLeftFirst, goRightFirst);}
}public static void main(String[] args) {Solution1 solution = new Solution1();// 示例 1int[][] fruits1 = {{2,8},{6,3},{8,6}};System.out.println(solution.maxTotalFruits(fruits1, 5, 4)); // 输出: 9// 示例 2int[][] fruits2 = {{0,9},{4,1},{5,7},{6,2},{7,4},{10,9}};System.out.println(solution.maxTotalFruits(fruits2, 5, 4)); // 输出: 14// 示例 3int[][] fruits3 = {{0,3},{6,4},{8,5}};System.out.println(solution.maxTotalFruits(fruits3, 3, 2)); // 输出: 0
}

详解

目的:将二维信息解耦为两个一维数组,便于后续使用双指针、二分查找等操作。

int n = fruits.length;// 提取位置和数量,方便操作
int[] positions = new int[n];
int[] amounts = new int[n];
for (int i = 0; i < n; i++) {positions[i] = fruits[i][0];  // 位置amounts[i] = fruits[i][1];    // 水果数量
}

前缀和(Prefix Sum)用来快速计算总和的一种方式
第 i+1 个累计值 = 第 i 个累计值 + 第 i 个位置的水果数

// 前缀和数组:prefix[i] 表示前 i 个位置的水果总数
int[] prefix = new int[n + 1];
for (int i = 0; i < n; i++) {prefix[i + 1] = prefix[i] + amounts[i];
}

拿示例 1: 举例
fruits = [[2,8], [6,3], [8,6]]
最终前缀和数组

prefix[i]含义对应的水果
prefix[0] = 0前 0 个位置的水果总数什么都没有
prefix[1] = 8前 1 个位置的水果总数位置 2:8 个
prefix[2] = 11前 2 个位置的水果总数位置 2+6:8+3=11
prefix[3] = 17前 3 个位置的水果总数位置 2+6+8:8+3+6=17

用前缀和快速算“某段区间的水果总数
🌰 例子 1:只摘位置 6 的水果(第 1 个位置)

  • left = 1, right = 1
  • 总数 = prefix[right+1] - prefix[left] = prefix[2] - prefix[1] = 11 - 8 = 3 ✅

🌰 例子 2:摘位置 6 和 8 的水果(第 1 和第 2 个位置)

  • left = 1, right = 2
  • 总数 = prefix[3] - prefix[1] = 17 - 8 = 9 ✅
    也就是 3 + 6 = 9

🌰 例子 3:摘所有水果(第 0 到第 2 个位置)

  • left = 0, right = 2
  • 总数 = prefix[3] - prefix[0] = 17 - 0 = 17 ✅

🌰 例子 4:只摘位置 2 的水果(第 0 个位置)

  • left = 0, right = 0
  • 总数 = prefix[1] - prefix[0] = 8 - 0 = 8
    这在滑动窗口中特别有用,因为 right 每移动一次,都要算一次区间和。

从左向右,滑动窗口
我们要在最多走 k 步的前提下,从起点 startPos 出发,摘到最多的水果。
maxFruits:就像一个“最高分记录”,通过 在 合理的 k 步条件下获取的最多水果数
外层循环:右指针 right 不断向右扩展
right 从 0 开始,一步步向右移动(0 → 1 → 2 → …)
每次 right 向右一格,就相当于我们想多摘一个位置的水果

for (int right = 0; right < n; right++) {

当前尝试的区间 [left, right]
拿示例 1: 举例
fruits = [[2,8], [6,3], [8,6]]
positions = [2, 6, 8]
那么 以下就会从

rightleft区间步数是否可达水果数maxFruits
00[2]388
10[2,6]5❌ → left=1--
1[6]13max(8,3)=8
21[6,8]39max(8,9)=9
int L = positions[left];      // 区间最左位置
int R = positions[right];     // 区间最右位置

计算区间区间步数
计算走完这个区间需要多少步
left <= right 缩小空间,保持空间合法性
steps > k 步数超过k步后,进入 while 调整左侧空间,使步数减少,
while (left <= right && steps > k)while 发现 septs 直到 空间合法后跳出循环

// 计算从 startPos 出发,走完 [L, R] 所需的最少步数
int steps = calculateSteps(startPos, L, R);// 如果步数超过了 k,说明走太远了,需要缩小窗口(移动 left)
while (left <= right && steps > k) {left++;  // 缩小左边界if (left > right) break;L = positions[left];R = positions[right];steps = calculateSteps(startPos, L, R);
}

校验合法性,并更新水果数
prefix[right + 1] - prefix[left] 这边用到了上面 所写的 前缀和 计算累计数量, left 跟 right 是 对应的坐标点,由于前缀和 第一个坐标点为 0 所以需要加1

prefix[i]含义对应的水果
prefix[0] = 0前 0 个位置的水果总数什么都没有
prefix[1] = 8前 1 个位置的水果总数位置 2:8 个
prefix[2] = 11前 2 个位置的水果总数位置 2+6:8+3=11
prefix[3] = 17前 3 个位置的水果总数位置 2+6+8:8+3+6=17

right + 1 前 right+1 个数的总和
left 前 left 个数的总和

amounts:     [ 8 ,  3 ,  6 ]↑    ↑    ↑
索引:         0    1    2prefix:  [0,  8,  11,  17]↑   ↑   ↑    ↑0   1   2    3

继续用示例1
fruits = [[2,8],[6,3],[8,6]]
比如 :获取 位置为6 的 水果, 由于 前缀和的缘故 第一个是固定值,后面的值是累加过来的
prefix[right + 1] - prefix[left] = prefix[2] - prefix[1] = 11 - 8 = 3
Math.max 对比 两数大小

// 如果当前窗口有效,更新最大水果数
if (left <= right) {int total = prefix[right + 1] - prefix[left];  // [left, right] 的水果总数maxFruits = Math.max(maxFruits, total);
}

计算步数
R <= startPos 从步数左侧走完的步数, 上面 for 循环 R 是在一步步从左走到右侧的
L >= startPos 从右侧走完的步数,L 因为上面空间缩小调整一步步走到右侧
获取 在 R 大于起始步数并 L 还没移动到右侧时 获取 中间的两种状态
(startPos - L) * 2 + (R - startPos) 先向左走到 L,再向右走到 R
(R - startPos) * 2 + (startPos - L) 先先向右走到 R,再向左走到 L
Math.min 获取两者最小步数方案,可能起始位置并不居中,导致 往回走步数减少减少的情况

// 计算从 startPos 出发,走完区间 [L, R] 所需的最少步数
private int calculateSteps(int startPos, int L, int R) {if (R <= startPos) {// 所有水果都在起点或左边:只向左走return startPos - L;} else if (L >= startPos) {// 所有水果都在起点或右边:只向右走return R - startPos;} else {// 水果分布在起点两侧// 方案1:先向左走到 L,再向右走到 Rint goLeftFirst = (startPos - L) * 2 + (R - startPos);// 方案2:先向右走到 R,再向左走到 Lint goRightFirst = (R - startPos) * 2 + (startPos - L);// 返回两种方案中步数更少的那个return Math.min(goLeftFirst, goRightFirst);}
}
http://www.dtcms.com/a/313232.html

相关文章:

  • 新手向:Python制作贪吃蛇游戏(Pygame)
  • Redis面试精讲 Day 9:Redis模块开发与扩展
  • 信创数据库-DM(达梦)数据库安装教程
  • Rust:如何访问 *.ini 配置文件?
  • 【项目日志|苍穹外卖】 Day1:项目环境搭建与架构设计
  • 反向代理+网关部署架构
  • Java学习第一百零二部分——API网关
  • Claude Code入门学习笔记(五)--Claude Code命令行输入
  • 大白话讲解MCP
  • 多种单文件版分析型数据库调用底层函数对比
  • [Oracle] TO_DATE()函数
  • cs285 lecture13
  • 手机端使用表格填写表单问题
  • 复现YOLOV5+训练指定数据集
  • STM32-ESP8266通过MQTT与阿里云通讯
  • MySQL连接算法和小表驱动大表的原理
  • 李宏毅深度学习教程 第8-9章 生成模型+扩散模型
  • 【Django】-7- 实现注册功能
  • 09.Redis 常用命令
  • Android 之 蓝牙通信(2.0 经典)
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-58,(知识点:硬件产品的功率优化)
  • C++中多线程和互斥锁的基本使用
  • 【RH124 问答题】第 8 章 监控和管理 Linux 进程
  • Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现实时食物水果的检测识别(C#代码UI界面版)
  • 使用 Spring Initializr 生成项目结构:Java 开发效率提升指南
  • 【QT】常⽤控件详解(二)windowOpacitycursorfontsetToolTipfocusPolicystyleSheet
  • 大语言模型涉及的一些概念(持续更新)
  • VisualStudio的一些开发经验
  • 思二勋:数字资产化与资产数权化是RWA运作的核心逻辑
  • AtCoder Beginner Contest 417