Problem: 904. 水果成篮
首先我们来分析一下本题的思路
fruit
数组,里面存放的是每棵树上水果的数量。当我们拿着两个篮子去采摘水果的时候,可以选择任意一颗树开始采摘{1, 2, 1}
指的就是第一棵树上有一个【1号水果】,第二棵树上有一个【2号水果】,第三棵树上有一个【1号水果】。所以若是我们从第一棵开始采摘的话,可以采摘到 2个1号水果和1个2号水果{1, 1, 1, 1, 1, 1}
,对于这个来说的话我们可以知道所摘的水果种类为1个,数量为6个那么本题我们就转换为了:找出一个最长的子数组长度,子数组中不超过两种类型的水果
接下去我们就来分析一下本题的算法原理
left
指针和一个right
指针,当这个right
指针在向后移动的时候,当其无法在继续后移的时候表示[left, right]
这段区间内的水果数量已经到达②了,此时我们要考虑去做【出窗口】的操作left
指针向右移动一格,那此时读者可以思考一下当前的kinds
会出现什么样的变化?[left, right]
这个区间中还存在着这个种类的水果[left, right]
区间中不包含了去除掉的这种水果的话,说明种类就会减少right
指针相应的变化情况,若是kinds
不做变化的话,right
也无需去变化,因为再去右移的话可能会增加水果的种类;若是kinds
变小的话,那么right
就可以右移了,此时水果的种类就可以增加上去那接下去呢,就让我们来看看,算法的执行过程是怎样的
right
在进行遍历的时候,其所对应的个数就++,那当这个数量大于2时就开始出窗口,所对应的则是left
左指针所指向的水果个数,但是呢这不是随便减的,当其减到【0】的时候,就要考虑从这个哈希表中删除掉这个相对应的水果了。好,以上就是有关【滑动窗口】相关的算法原理分析,详情代码见【Code】
那有同学问:为何不直接讲滑动窗口相关的算法呢,而是每次都要讲一下暴力的解法,这不是多此一举吗?
有读者一定会疑惑,已经使用【滑动窗口】去做了优化为什么还要再去优化呢?我们可以来看看滑动窗口的代码提交之后的结果
那怎么去做一个优化呢?
fruit
数组最大的个数为 $ 10^5 $,那我们其实可以不用使用哈希表去存储,而是直接利用【数组】去进行存放,因为对于数组的每个元素来说其实就是一种映射,和哈希表其实是差不多的原理kinds
变量用于代替哈希表的个数统计int hash[100001] = { 0 };
for(int left = 0, right = 0, kinds = 0; right < n; ++right)
erase
,而是直接kinds--
去控制当前窗口中的水果个数if(hash[fruits[left]] == 0)
// hash.erase(fruits[left]);
kinds--;
kinds
的个数进行一个累加// 一进来判断发现水果的种类为0的话,则水果种类增加一种
if(hash[fruits[right]] == 0)
kinds++;
kinds
做出判断即可fi(kinds > 2)
具体代码可以参照【Code】部分,我们来看到提交之后的执行结果可以观察到性能确实得到了大幅度的提升
接下去我们来分析一下时间复杂度
首先是对于时间复杂度而言,【滑动窗口】部分的代码, 我们使用
left
和right
双指针去遍历查找整个数组, 并且在查找的过程中遇到水果数量 > 2需要去做出窗口的操作,因为erase()
的复杂度为 O ( n ) O(n) O(n), 所以在最坏的情况下复杂度可以到达 O ( n 2 ) O(n^2) O(n2)
而对于数组优化来说, 虽免去了哈希表,没了计数的功效。但是我们只是去做了遍历的操作,中间循环的过程使用的是
kinds
作的标记操作,不涉及erase()
,因此最坏的复杂度因为 O ( n ) O(n) O(n)
对于空间复杂度来说,两种优化的方法并没有涉及额外空间的申请, 所以空间复杂度即为: O ( 1 ) O(1) O(1)
接下去是代码部分
class Solution {
public:
int totalFruit(vector<int>& fruits) {
int n = fruits.size();
unordered_map<int, int>hash;
int max_len = INT_MIN;
for(int left = 0, right = 0; right < n; ++right)
{
// 1.进窗口
hash[fruits[right]]++;
// 当水果的种类多于2种的时候,开始出窗口
if(hash.size() > 2)
{
// 2.出窗口【此时left不能后移,因此要删除该水果】
hash[fruits[left]]--;
// 如果当前水果种类的数量到0的话,将其从哈希表中删除
if(hash[fruits[left]] == 0)
hash.erase(fruits[left]);
left++;
}
// 更新结果
max_len = max(max_len, right - left + 1);
}
return max_len == INT_MIN ? 0 : max_len;
}
};
class Solution {
public:
int totalFruit(vector<int>& fruits) {
int n = fruits.size();
//unordered_map hash;
int hash[100001] = { 0 };
int max_len = INT_MIN;
for(int left = 0, right = 0, kinds = 0; right < n; ++right)
{
// 一进来判断发现水果的种类为0的话,则水果种类增加一种
if(hash[fruits[right]] == 0)
kinds++;
// 1.进窗口
hash[fruits[right]]++;
// 判断当前哈希表中水果的种类是否超过两种
if(kinds > 2)
{
// 2.出窗口
hash[fruits[left]]--;
// 如果在出窗口之后当前水果的数量为0的话,则从哈希表中删除该水果
if(hash[fruits[left]] == 0)
// hash.erase(fruits[left]);
kinds--;
left++;
}
// 3.更新最大长度
max_len = max(max_len, right - left + 1);
}
return max_len == INT_MIN ? 0 : max_len;
}
};