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 步内能收集到的最大水果数量。关键在于如何通过两个步数条件来调整窗口的左右边界,确保移动步数的限制被满足。
