LeetCode 904. 水果成篮【不定长滑窗+哈希表】1516

本文属于「征服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^5
  • 0 <= 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 越短越合法/求最长/最大」。

相关推荐
老四啊laosi2 小时前
[双指针] 8. 四数之和
算法·leetcode·四数之和
汀、人工智能2 小时前
[特殊字符] 第24课:反转链表
数据结构·算法·链表·数据库架构··反转链表
田梓燊2 小时前
leetcode 41
数据结构·算法·leetcode
_深海凉_2 小时前
LeetCode热题100-三数之和
算法·leetcode·职场和发展
y = xⁿ3 小时前
【LeetCode】双指针合集
算法·leetcode
2601_949539453 小时前
家用新能源 SUV 核心技术科普:后排娱乐、空间工程与混动可靠性解析
大数据·网络·人工智能·算法·机器学习
生信研究猿3 小时前
一个整数转换为二进制
leetcode
凌波粒3 小时前
LeetCode--18.四数之和(双指针法)
数据结构·算法·leetcode
笨笨饿3 小时前
33_顺序表(待完善)
linux·服务器·c语言·嵌入式硬件·算法·学习方法