双指针与滑动窗口中等1 种解法
#904水果成篮
寻找只包含至多两种值的最长连续子数组,对应两个篮子可采摘的最多水果数。
#数组#哈希表#滑动窗口
原题
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
- 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
- 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
- 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。
示例 1:
输入:fruits = [1,2,1] 输出:3 解释:可以采摘全部 3 棵树。
示例 2:
输入:fruits = [0,1,2,2] 输出:3 解释:可以采摘 [1,2,2] 这三棵树。 如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。
示例 3:
输入:fruits = [1,2,3,2,2] 输出:4 解释:可以采摘 [2,3,2,2] 这四棵树。 如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。
示例 4:
输入:fruits = [3,3,3,1,2,1,1,2,3,3,4] 输出:5 解释:可以采摘 [1,2,1,1,2] 这五棵树。
提示:
1 <= fruits.length <= 1050 <= fruits[i] < fruits.length
解题主线
- 题意等价于至多包含两种水果的最长连续窗口。
- 频次表记录窗口内每种水果的数量;类型超过两种时从左侧收缩,频次归零后删除键。
- 窗口恢复合法后,right - left + 1 就是以当前右端结尾的最长合法区间。
解法 1:至多两类的滑动窗口
右端加入水果并更新频次;种类超过两种时移动左端并清理零频次,持续更新合法窗口最大长度。
-
时间复杂度: O(n)
-
空间复杂度: O(1),窗口内至多维护 3 种水果
import java.util.HashMap;
import java.util.Map;
final class Solution {
public int totalFruit(int[] fruits) {
// 窗口不变量:合法时只包含至多两种水果。
// 加入第三种后持续移动左端,频次归零必须删键才能恢复约束。
// 收缩完成后,当前窗口是以 right 结尾的最长合法候选。
Map<Integer, Integer> counts = new HashMap<>();
int left = 0;
int maximum = 0;
for (int right = 0; right < fruits.length; right++) {
counts.merge(fruits[right], 1, Integer::sum);
while (counts.size() > 2) {
int removed = fruits[left++];
int remaining = counts.get(removed) - 1;
if (remaining == 0) counts.remove(removed);
else counts.put(removed, remaining);
}
maximum = Math.max(maximum, right - left + 1);
}
return maximum;
}
}边界与易错点
- 必须连续从一排树中采摘,不能任选全局出现最多的两种水果。
- 频次减到 0 时要删除键,否则 basket.size() 不会恢复到 2。
- 旧注释称哈希表记录最后索引,实际实现记录的是频次;整理后统一语义。
整理来源
由旧仓库源码复核、去重并整理;展示代码已按 Java 21 语义修正明显问题。
leetcode/src/main/java/medium/Q904_totalFruit.java