本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
- 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
- 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
- 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。
示例 1:
java
输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。
示例 2:
java
输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。
示例 3:
java
输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。
示例 4:
java
输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。
提示:
1 <= fruits.length <= 10^50 <= fruits[i] < fruits.length
解法 不定长滑窗+哈希表
找一个最长连续子数组,满足子数组中至多有两种数字,返回子数组的长度。
子数组越长,包含的元素越多,越可能不满足题目要求;反之,子数组越短,包含的元素越少,越可能满足题目要求。本题属于「越短越合法」,有这种性质的题目,可以用滑动窗口解决。
枚举子数组的右端点 r i g h t right right 。同时用一个哈希表 c n t cnt cnt 维护子数组内每个元素的出现次数。
如果 f r u i t s [ r i g h t ] fruits[right] fruits[right] 加入哈希表后,发现哈希表的大小超过了 2 2 2 ,那么子数组不满足要求。移动子数组的左端点 l e f t left left ,把 f r u i t s [ l e f t ] fruits[left] fruits[left] 的出现次数减一,直到哈希表中的元素种数等于 2 2 2 。
⚠注意:如果 f r u i t s [ l e f t ] fruits[left] fruits[left] 的出现次数变成 0 0 0 ,需要从 c n t cnt cnt 中移除,表示子数组内少了一种元素。如果不移除,我们无法通过 c n t cnt cnt 的大小判断窗口中的元素种数。
java
class Solution {
public int totalFruit(int[] fruits) {
int ans = 0;
int left = 0;
Map<Integer, Integer> cnt = new HashMap<>();
for (int right = 0; right < fruits.length; right++) {
cnt.merge(fruits[right], 1, Integer::sum); // fruits[right] 进入窗口
while (cnt.size() > 2) { // 不满足要求
int out = fruits[left];
cnt.merge(out, -1, Integer::sum); // fruits[left] 离开窗口
if (cnt.get(out) == 0) {
cnt.remove(out);
}
left++;
}
ans = Math.max(ans, right - left + 1);
}
return ans;
}
}
cpp
class Solution {
public:
int totalFruit(vector<int>& fruits) {
int ans = 0, left = 0;
unordered_map<int, int> cnt;
for (int right = 0; right < fruits.size(); right++) {
cnt[fruits[right]]++; // fruits[right] 进入窗口
while (cnt.size() > 2) { // 不满足要求
int out = fruits[left];
cnt[out]--; // fruits[left] 离开窗口
if (cnt[out] == 0) {
cnt.erase(out);
}
left++;
}
ans = max(ans, right - left + 1);
}
return ans;
}
};
python
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
ans = left = 0
cnt = defaultdict(int)
for right, in_ in enumerate(fruits):
cnt[in_] += 1 # fruits[right] 进入窗口
while len(cnt) > 2: # 不满足要求
out = fruits[left]
cnt[out] -= 1 # fruits[left] 离开窗口
if cnt[out] == 0:
del cnt[out]
left += 1
ans = max(ans, right - left + 1)
return ans
rust
use std::collections::HashMap;
impl Solution {
pub fn total_fruit(fruits: Vec<i32>) -> i32 {
let mut ans = 0;
let mut left = 0;
let mut cnt = HashMap::new();
for (right, &x) in fruits.iter().enumerate() {
*cnt.entry(x).or_insert(0) += 1; // fruits[right] 进入窗口
while cnt.len() > 2 { // 不满足要求
let out = fruits[left];
*cnt.entry(out).or_insert(0) -= 1; // fruits[left] 离开窗口
if cnt[&out] == 0 {
cnt.remove(&out);
}
left += 1;
}
ans = ans.max(right - left + 1);
}
ans as _
}
}
go
func totalFruit(fruits []int) (ans int) {
cnt := map[int]int{}
left := 0
for right, in := range fruits {
cnt[in]++ // fruits[right] 进入窗口
for len(cnt) > 2 { // 不满足要求
out := fruits[left]
cnt[out]-- // fruits[left] 离开窗口
if cnt[out] == 0 {
delete(cnt, out)
}
left++
}
ans = max(ans, right-left+1)
}
return
}
js
var totalFruit = function(fruits) {
let ans = 0, left = 0;
const cnt = new Map();
for (let right = 0; right < fruits.length; right++) {
cnt.set(fruits[right], (cnt.get(fruits[right]) ?? 0) + 1); // fruits[right] 进入窗口
while (cnt.size > 2) { // 不满足要求
const out = fruits[left];
cnt.set(out, cnt.get(out) - 1); // fruits[left] 离开窗口
if (cnt.get(out) === 0) {
cnt.delete(out);
}
left++;
}
ans = Math.max(ans, right - left + 1);
}
return ans;
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n) ,其中 n n n 是 f r u i t s fruits fruits 的长度。
- 空间复杂度: O ( 1 ) O(1) O(1) ,在任意时刻,哈希表中至多有 3 3 3 个键值对( 3 3 3 种不同元素)。
专题训练
滑动窗口题单中的「2.1 越短越合法/求最长/最大」。