题目链接
题目描述
在一排果树上采摘水果,每棵树结一种水果(用整数表示种类)。你只有两个篮子,每个篮子只能装一种水果。求最多能采摘的水果数量,要求采摘的水果种类不能超过两种。
解法分析:滑动窗口法
核心思路
使用滑动窗口技术维护一个区间,确保区间内最多包含2种水果。当区间内水果种类超过2种时,移动左指针缩小窗口,直到满足条件。窗口的最大长度即为能采摘的最多水果数量。
代码实现
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
m = {} # 记录窗口内水果种类及其数量
l = ans = 0 # 左指针、最大水果数量
for i in range(len(fruits)):
# 更新当前水果的数量
m[fruits[i]] = m.get(fruits[i], 0) + 1
# 当水果种类超过2种时,收缩左边界
while len(m) > 2:
m[fruits[l]] -= 1
if m[fruits[l]] == 0:
del m[fruits[l]] # 移除数量为0的水果种类
l += 1 # 左指针右移
# 更新最大水果数量(窗口长度)
ans = max(ans, i - l + 1)
return ans
代码解析
-
初始化:
m:字典,用于记录当前窗口内每种水果的数量l:滑动窗口的左指针,初始位置为0ans:记录能采摘的最大水果数量,初始为0
-
遍历果树:
- 右指针
i从0开始遍历每棵果树 m[fruits[i]] = m.get(fruits[i], 0) + 1:将当前果树的水果加入窗口,并更新其数量- 使用
dict.get(key, default)方法避免键不存在时的错误
- 右指针
-
调整窗口:
- 当
len(m) > 2时,说明窗口内水果种类超过2种 m[fruits[l]] -= 1:减少左指针处水果的数量- 若水果数量变为0,则从字典中删除该水果种类
l += 1:左指针右移,缩小窗口范围,直到窗口内水果种类≤2种
- 当
-
更新结果:
ans = max(ans, i - l + 1):计算当前窗口长度并更新最大水果数量- 窗口长度为
i - l + 1,表示从左指针到右指针的果树数量
关键逻辑图示
以输入fruits = [1,2,3,2,2]为例:
- i=0,水果1:
m={1:1},种类数1≤2,窗口长度1,ans=1
- i=1,水果2:
m={1:1, 2:1},种类数2≤2,窗口长度2,ans=2
- i=2,水果3:
m={1:1, 2:1, 3:1},种类数3>2,进入收缩:m[1] -=1 → m[1]=0,删除1,l=1
m={2:1, 3:1},种类数2≤2,窗口长度2-1+1=2,ans=2
- i=3,水果2:
m={2:2, 3:1},种类数2≤2,窗口长度3-1+1=3,ans=3
- i=4,水果2:
m={2:3, 3:1},种类数2≤2,窗口长度4-1+1=4,ans=4
- 最终返回4,即最多能采摘4个水果
复杂度分析
- 时间复杂度:O(n),其中n是果树的数量。每个果树最多被左右指针各访问一次,总操作次数不超过2n。
- 空间复杂度:O(1),字典中最多存储2种水果的信息,空间使用与输入规模无关。
总结
该解法通过滑动窗口和哈希表高效解决了水果成篮问题,核心在于维护窗口内水果种类不超过2种。当种类超过时收缩左边界,确保窗口有效性。这种方法是解决"最多包含k种元素"的子数组问题的经典解法,时间和空间复杂度均为最优。
滑动窗口的关键在于:
- 定义窗口的左右指针
- 维护窗口内的状态(本题中是水果种类数)
- 当状态不满足条件时调整窗口边界
- 实时更新最优解
这种思路可以扩展到类似问题,如"最多包含k个不同字符的最长子串"等,具有很强的通用性。