【Leetcode】904. 水果成篮

文章目录

题目

题目链接🔗

题目描述:

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruitsi 是第 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 <= 105

0 <= fruitsi < fruits.length

思路

这是一个滑动窗口问题,我们需要找到最长的连续子数组,其中最多包含两种不同的水果类型。具体步骤如下:

  1. 使用两个指针(left 和 right)维护一个窗口。
  2. 使用一个哈希表(或数组)来记录窗口内水果类型的计数。
  3. 扩展右指针,添加新水果到窗口。
  4. 如果窗口内类型超过两种,移动左指针,移除类型直到类型数 <= 2。
  5. 每次更新最大窗口大小。

这种方法确保我们高效地找到满足条件的最长子数组。

代码

C++

cpp 复制代码
class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int n = fruits.size();
        unordered_map<int, int> cnt;
        int left = 0, ans = 0;
        for (int right = 0; right < n; ++right) {
            ++cnt[fruits[right]];
            while (cnt.size() > 2) {
                --cnt[fruits[left]];
                if (cnt[fruits[left]] == 0) {
                    cnt.erase(fruits[left]);
                }
                ++left;
            }
            ans = max(ans, right - left + 1);
        }
        return ans;
    }
};

Java

java 复制代码
class Solution {
    public int totalFruit(int[] fruits) {
        int n = fruits.length;
        Map<Integer, Integer> cnt = new HashMap<Integer, Integer>();
        int left = 0, ans = 0;
        for (int right = 0; right < n; ++right) {
            cnt.put(fruits[right], cnt.getOrDefault(fruits[right], 0) + 1);
            while (cnt.size() > 2) {
                cnt.put(fruits[left], cnt.get(fruits[left]) - 1);
                if (cnt.get(fruits[left]) == 0) {
                    cnt.remove(fruits[left]);
                }
                ++left;
            }
            ans = Math.max(ans, right - left + 1);
        }
        return ans;
    }
}

Python

python 复制代码
class Solution:
    def totalFruit(self, fruits: List[int]) -> int:
        cnt = {}
        left, ans = 0, 0
        for right, fruit in enumerate(fruits):
            cnt[fruit] = cnt.get(fruit, 0) + 1
            while len(cnt) > 2:
                cnt[fruits[left]] -= 1
                if cnt[fruits[left]] == 0:
                    del cnt[fruits[left]]
                left += 1
            ans = max(ans, right - left + 1)
        return ans

复杂度分析

时间复杂度

  • O(N):每个元素最多被访问两次(右指针和左指针),N 为数组长度。

空间复杂度

  • O(1):哈希表最多存储3个键(两种类型 + 临时第三种),但最坏情况下为 O(N),实际由于类型有限,为常量。

结果

  • 时间复杂度:O(N)
  • 空间复杂度:O(1)
  • 通过所有测试用例

思路扩展:泛化到最多K种水果

如果问题扩展为允许使用K个篮子(即最多K种不同的水果类型),本质上变成了求解"最长连续子数组中包含最多K种不同元素"的长度。这是一个经典的滑动窗口问题变体,与原问题(K=2)类似。我们可以稍作修改来处理任意K(假设K >= 1)。

修改思路:

  1. 仍然使用两个指针(left 和 right)维护滑动窗口。
  2. 使用哈希表(Map或Dict)记录窗口内每种水果类型的计数。
  3. 扩展右指针,添加新水果到窗口,并更新计数。
  4. 如果窗口内独特类型数量(哈希表大小)超过K,则移动左指针,减少对应类型的计数,直到独特类型 <= K(如果计数降到0,则移除该类型)。
  5. 每次有效窗口时,更新最大长度(right - left + 1)。
  6. 这个方法可以无缝处理K=2的原问题,也适用于更大的K。

这种扩展保持了高效性,时间复杂度不变。

边界考虑:

  • 如果K=0,返回0(但通常K>=1)。
  • 如果K >= 水果总类型数,则答案为整个数组长度。
  • 处理空数组或单一类型的情况自然包含。

代码(泛化版本,添加参数K)

假设函数签名修改为int totalFruit(vector<int>& fruits, int K)(类似地为其他语言)。

C++

cpp 复制代码
class Solution {
public:
    int totalFruit(vector<int>& fruits, int K) {
        int n = fruits.size();
        unordered_map<int, int> cnt;
        int left = 0, ans = 0;
        for (int right = 0; right < n; ++right) {
            ++cnt[fruits[right]];
            while (cnt.size() > K) {
                --cnt[fruits[left]];
                if (cnt[fruits[left]] == 0) {
                    cnt.erase(fruits[left]);
                }
                ++left;
            }
            ans = max(ans, right - left + 1);
        }
        return ans;
    }
};

Java

java 复制代码
class Solution {
    public int totalFruit(int[] fruits, int K) {
        int n = fruits.length;
        Map<Integer, Integer> cnt = new HashMap<Integer, Integer>();
        int left = 0, ans = 0;
        for (int right = 0; right < n; ++right) {
            cnt.put(fruits[right], cnt.getOrDefault(fruits[right], 0) + 1);
            while (cnt.size() > K) {
                cnt.put(fruits[left], cnt.get(fruits[left]) - 1);
                if (cnt.get(fruits[left]) == 0) {
                    cnt.remove(fruits[left]);
                }
                ++left;
            }
            ans = Math.max(ans, right - left + 1);
        }
        return ans;
    }
}

Python

python 复制代码
class Solution:
    def totalFruit(self, fruits: List[int], K: int) -> int:
        cnt = {}
        left, ans = 0, 0
        for right, fruit in enumerate(fruits):
            cnt[fruit] = cnt.get(fruit, 0) + 1
            while len(cnt) > K:
                cnt[fruits[left]] -= 1
                if cnt[fruits[left]] == 0:
                    del cnt[fruits[left]]
                left += 1
            ans = max(ans, right - left + 1)
        return ans

复杂度分析(扩展版本)

时间复杂度

  • O(N):每个元素最多被右指针和左指针访问一次,N 为数组长度。即使K变化,复杂度不变。

空间复杂度

  • O(K):哈希表最多存储K+1个键(临时超过时),最坏情况下为O(min(N, 水果类型数))。

结果(扩展版本)

  • 该扩展算法适用于任意K,通过所有原测试用例(K=2时与原问题一致),并可处理更大K的场景。
  • 示例:对于fruits = 1,2,3,2,2 和 K=3,输出5(整个数组),因为包含三种类型(1,2,3)。

总结

水果成篮问题本质上是求最长子数组中最多两种元素的长度,使用滑动窗口和哈希表高效维护类型计数。关键在于:

  1. 窗口维护:确保类型不超过两种,动态调整左边界。
  2. 线性时间:避免暴力枚举,实现高效求解。
  3. 边界处理:空数组或单一类型自然处理。

通过将独特类型限制从2泛化到K,我们只需在滑动窗口的收缩条件中调整为cnt.size() > K,即可实现灵活扩展。这体现了滑动窗口在"最多K种元素最长子数组"问题中的通用性。如果K很大,可进一步优化为检查整个数组的独特类型,但当前实现已高效。https://leetcode.cn/problems/fruit-into-baskets/description/?envType=daily-question&envId=2025-08-04

相关推荐
Jerry5 小时前
LeetCode 28. 找出字符串中第一个匹配项的下标
算法
Jerry7 小时前
LeetCode 459. 重复的子字符串
算法
海石10 小时前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode
海石10 小时前
【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快
算法·leetcode
Jerry12 小时前
LeetCode 151. 反转字符串中的单词
算法
咖啡星人k14 小时前
从Vibe Coding到Prompt Engineering:AI编程的新技能树
面试·职场和发展·ai编程
a11177614 小时前
LM 算法迭代过程动画演示(SLAM)
算法
头茬韭菜14 小时前
Context 的生死抉择:四层压缩、截断算法与 Session Memory
算法·ai
Jerry15 小时前
LeetCode 541. 反转字符串 II
算法
Jerry15 小时前
LeetCode 344. 反转字符串
算法