2106. 摘水果
2106. 摘水果
初始理解问题
首先,我们需要明确题目要求:
-
输入:
fruits
:一个列表,其中每个元素是一个包含两个整数的列表[position, amount]
,表示在位置position
上有amount
个水果。startPos
:一个整数,表示起始位置。k
:一个整数,表示最多可以移动的步数。
-
输出:
- 一个整数,表示在最多移动
k
步的情况下,能够收集到的最大水果数量。
- 一个整数,表示在最多移动
-
移动规则:
- 每次可以向左或向右移动一步(即位置 +1 或 -1)。
- 移动的总步数不能超过
k
。 - 可以收集经过的位置上的水果(假设收集水果不需要额外步数,或者是在移动过程中自动收集)。
解题思路
为了找到在最多 k
步内能收集到的最大水果数量,我们需要考虑所有可能的移动路径,这些路径的移动步数不超过 k
,然后计算在这些路径上能收集到的水果总数。
直接枚举所有可能的路径显然不现实,因为步数可以很大。因此,我们需要一种更高效的方法。
关键观察
-
移动范围:
- 最远可以移动到
startPos - k
到startPos + k
的位置。 - 但是,由于可以向左或向右移动,实际能到达的最远位置可能更受限制。
- 最远可以移动到
-
移动策略:
- 可以看作是在一个区间内移动,这个区间由
startPos
和k
决定。 - 我们需要找到一个连续的区间
[L, R]
,使得从startPos
出发,移动到这个区间的最左或最右,然后可能来回移动,总步数不超过k
。
- 可以看作是在一个区间内移动,这个区间由
-
滑动窗口:
- 可以使用滑动窗口的方法来维护一个区间
[left, right]
,表示当前考虑的水果位置区间。 - 通过调整
left
和right
来确保移动步数不超过k
,并计算窗口内的水果总数。
- 可以使用滑动窗口的方法来维护一个区间
具体步骤
-
预处理:
- 首先,将
fruits
按照位置排序(假设输入已经按位置排序,因为题目没有明确说明,但通常这类问题会保证输入有序)。 - 使用
bisect
模块来快速找到起始位置附近的水果。
- 首先,将
-
初始窗口:
left
是第一个位置 >=startPos - k
的水果的索引。right
是第一个位置 >=startPos + 1
的水果的索引(即位置 <=startPos
的最大索引 + 1)。- 初始时,计算从
left
到right - 1
的水果总数,即从startPos - k
到startPos
的水果数。
-
滑动窗口扩展:
- 然后,逐步向右扩展
right
,即考虑更远的位置。 - 对于每个新的
right
,将对应的水果数量加入当前总和s
。 - 然后,调整
left
以确保从startPos
出发,移动到fruits[right][0]
或fruits[left][0]
的总步数不超过k
。- 移动步数的计算:
- 从
startPos
移动到fruits[right][0]
然后可能来回移动。 - 两个主要的移动模式:
- 先向右到
fruits[right][0]
,然后向左到fruits[left][0]
再返回。 - 先向左到
fruits[left][0]
,然后向右到fruits[right][0]
再返回。
- 先向右到
- 具体步数计算:
- (先右后左)从
startPos
到fruits[right][0]
然后到fruits[left][0]
的步数为(fruits[right][0] - startPos) + (fruits[right][0] - fruits[left][0])
=2 * fruits[right][0] - startPos - fruits[left][0]
。 - (先左后右)或者从
startPos
到fruits[left][0]
然后到fruits[right][0]
的步数为(startPos - fruits[left][0]) + (fruits[right][0] - fruits[left][0])
=fruits[right][0] + startPos - 2 * fruits[left][0]
。
- (先右后左)从
- 简化后的条件:
fruits[right][0] * 2 - fruits[left][0] - startPos > k
或fruits[right][0] - fruits[left][0] * 2 + startPos > k
。
- 从
- 如果步数超过
k
,则移动left
向右,减少窗口内的水果数量。
- 移动步数的计算:
- 然后,逐步向右扩展
-
更新最大值:
- 在每次调整
left
和right
后,计算当前窗口内的水果总数s
,并更新最大值ans
。
- 在每次调整
代码解释
class Solution:def maxTotalFruits(self, fruits: List[List[int]], startPos: int, k: int) -> int:left = bisect_left(fruits, [startPos - k]) # 向左最远能到 fruits[left][0]right = bisect_left(fruits, [startPos + 1]) # 位置 <= startPos 的最大下标 + 1ans = s = sum(f[1] for f in fruits[left: right]) # 从 fruits[left][0] 到 startPos 的水果数# 枚举最右走到 fruits[right][0]while right < len(fruits) and fruits[right][0] <= startPos + k:s += fruits[right][1]while left < right and (fruits[right][0] * 2 - fruits[left][0] - startPos > k or fruits[right][0] - fruits[left][0] * 2 + startPos > k):s -= fruits[left][1] # fruits[left][0] 太远了left += 1ans = max(ans, s) # 更新答案最大值right += 1 # 继续枚举下一个最右位置return ans
-
初始
left
和right
的确定:left = bisect_left(fruits, [startPos - k])
:- 使用二分查找找到第一个位置 >=
startPos - k
的水果的索引。这是向左最远可以到达的位置。
- 使用二分查找找到第一个位置 >=
right = bisect_left(fruits, [startPos + 1])
:- 使用二分查找找到第一个位置 >=
startPos + 1
的水果的索引。这相当于位置 <=startPos
的最大索引 + 1。
- 使用二分查找找到第一个位置 >=
-
**初始水果总数
ans
和s
**:ans = s = sum(f[1] for f in fruits[left: right])
:- 计算从
left
到right - 1
的水果数量总和,即从startPos - k
到startPos
的水果数。这是初始窗口内的水果总数。
- 计算从
-
滑动窗口扩展:
while right < len(fruits) and fruits[right][0] <= startPos + k:
:- 只要
right
没有超出fruits
的范围,并且当前fruits[right][0]
不超过startPos + k
,就继续扩展。
- 只要
s += fruits[right][1]
:- 将当前
right
位置的水果数量加入总和s
。
- 将当前
- **调整
left
**:while left < right and (条件)
:- 当
left
没有超过right
,并且移动步数超过k
时,移动left
向右。 - 条件:
fruits[right][0] * 2 - fruits[left][0] - startPos > k
:- 表示从
startPos
到fruits[right][0]
然后到fruits[left][0]
的步数超过k
。
- 表示从
fruits[right][0] - fruits[left][0] * 2 + startPos > k
:- 表示从
startPos
到fruits[left][0]
然后到fruits[right][0]
的步数超过k
。
- 表示从
- 如果满足任一条件,则
s -= fruits[left][1]
并left += 1
,即减少窗口左边界的水果数量。
- 当
- 更新最大值:
ans = max(ans, s)
:- 每次调整后,计算当前窗口内的水果总数
s
,并更新最大值ans
。
- 每次调整后,计算当前窗口内的水果总数
right += 1
:- 继续向右扩展窗口。
-
返回结果:
return ans
:- 返回能够收集到的最大水果数量。
为什么这样有效
- 滑动窗口:通过维护一个窗口
[left, right]
,确保窗口内的水果可以通过不超过k
步的移动收集到。 - 步数计算:通过两个不等式确保从
startPos
出发,移动到窗口的最左或最右位置的总步数不超过k
。 - 高效性:利用二分查找快速定位初始窗口,并通过滑动窗口线性遍历可能的区间,保证了算法的高效性。
可能的疑问
-
**为什么
right
初始是bisect_left(fruits, [startPos + 1])
**:- 这是为了找到位置 <=
startPos
的最大索引 + 1。即right
初始指向第一个位置 >startPos
的水果,因此fruits[left: right]
包含位置 <=startPos
的水果。
- 这是为了找到位置 <=
-
步数计算的两个条件:
- 这两个条件分别对应两种移动策略:
- 先向右到最远,然后向左。
- 先向左到最远,然后向右。
- 通过这两个条件可以确保总步数不超过
k
。
- 这两个条件分别对应两种移动策略:
总结
该解决方案通过滑动窗口和二分查找高效地找到了在最多 k
步内能收集到的最大水果数量。关键在于如何通过两个步数条件来调整窗口的左右边界,确保移动步数的限制被满足。