STL之map信笔

没有使用map的办法:     

      看过前两篇文章的佬们应该能看出来,笔者完全是一个代码小白,在最近做到一道题,名为水果成篮,取自Leecode904题:
https://leetcode.cn/problems/fruit-into-baskets/description/

       有一些自己的理解想跟大家分享一下。

       刚理解了滑动窗口的我兴致冲冲地去做,结果碰了一鼻子灰,比如一开始是想用一个容量为2的数组Type来表示篮子,那么就会出现一个问题,在我们要往里面新装入水果的时候,该如何知道其中哪个位置需要被替换呢?是该保留苹果还是香蕉?这个问题我们放在后面去探讨。

       起初,我用left指向当前篮子里的第一个水果,right指向即将往里新装入的水果,如果right指向的水果类型和之前存入Type的两个水果类型不同,那么就让left前移,直到篮子里只剩下含right指向的水果类型的两种水果,而最开始为了保证篮子是空的,根据题目条件 fruit 中的元素都是≥0的,因此可以直接将空篮定义为{-1,-1},那么问题来了,我怎么知道篮子里何时只剩下两种水果?

       很容易想到的思路是先保存 fruit[right-1] 指向的类型作为将被保存的类型,即

changeType = Type[0] == fruit[right-1] ? Type[1] : Type[0];

此时changeType就代表需要被替换的类型,随后便想到,我要找的无非是让 left 与 right 之间只有两种类型的元素,那么在 right 此时已经确定是一个新元素的情况下,我们只需要遍历从 left 到 right 之间 changeType出现的最后的位置,然后让 left 指向该位置的后一个,便可以完成 left 的前移操作,代码看起来会更清晰:

int i;
for (i=left;i<right;i++) {
if(fruits[i]==changeType)
left=i;
}

       现在回到我们最开始的问题,我怎么知道哪个位置需要被替换?此时我们已经知道,菜篮子里不就俩类型的水果吗,不是第一个就是第二个呗,于是只要做一次判断:

if(Type[0] == changeType)
Type[0]=fruit[right];
else 
Type[1]=fruit[right];

这样就可以得到需要替换的位置,理清楚这几个问题,代码就行云流水版写出来了:
 

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int n = fruits.size();
        int Type[2] = {-1, -1};
        int ans = 0, left = 0, right = 0, Sublength = 0, changeType = -1;
        while (right < n) {
            if (Type[0] == -1 && Type[1] != fruits[right]) {
                Type[0] = fruits[right];
                    Sublength = right - left+1; // 计算当前长度
             ans = ans >= Sublength ? ans : Sublength; // 计算最长子数组长度
                right++;
              
                continue;
            } else if (Type[1] == -1 && Type[0] != fruits[right]) {
                Type[1] = fruits[right];
                       Sublength = right - left+1; // 计算当前长度
             ans = ans >= Sublength ? ans : Sublength; // 计算最长子数组长度
                right++;
                continue;
            }
           
            if (fruits[right] == Type[0] ||
                fruits[right] == Type[1]){ // 如果新水果和篮子类型一致就放入
                   Sublength = right - left+1; // 计算当前长度
             ans = ans >= Sublength ? ans : Sublength; // 计算最长子数组长度
                right++;
               
                }
            else { 
               
               
                if (Type[0] !=
                    fruits[right-1]) { // 先找出多余类型的水果放在哪个篮子
                    changeType=Type[0];
                    Type[0] = fruits[right]; // 进行替换
                } else if (Type[1]!= fruits[right-1]) {
                     changeType=Type[1];
                    Type[1] = fruits[right];
                }
                int i;
                for (i=left;i<right;i++) {
                if(fruits[i]==changeType)
                    left=i;
                }
               left++;
            }
          
        }
        return ans;
    }
};

可以看到复杂度还是很低的,但是代码非常冗余,且该题花费了笔者一个小时的时间,实在是太废柴了,有没有什么更牛逼的办法能更快实现呢?

std::map

       map,一种STL关联容器,在这里不过多赘述map的底层逻辑,只简单讲一下在这道题中使用,首先map是一种键值对数据结构,说白话就是里面存着的是一对一对的数据,(唉,笔者还是单身,使用的数据结构却都是成双成对的,开个玩笑),一个叫做key,代表着关键字,一个叫做value,代表着存储的值,因此这种数据结构类型被称为键值对,这种数据结构允许我们直接使用key用来搜索value,至于为什么咱们现在不用管这么多,那如何使用它呢?拿这道题举例子,我们直接定义篮子:

unordered_map<int, int> cnt

这里有人要问了,不是讲map吗?这 unordered_map是什么东西?其实它也是map的一种,只不过底层是用哈希表实现的,而map使用的是红黑树,扯远了哈,大家只要知道他们的使用方法是一样的,这里使用 unordered_map也只是为了能使代码运行快那么一点点,第一个int代表关键字是整型,第二个int代表value是整型,cnt则是我们定义的map类型的名字,在这题中,我们让key代表水果的种类,value代表水果在篮子中的数量,是不是很完美,我们现在对它进行遍历,看看会有什么不一样,首先在刚刚,我们需要对初始篮子定义为{-1,-1}的空篮,其实根本不用那么麻烦,只需要利用map的特性

如果 fruits[right] 还没有出现在map中,cnt[fruits[right]] 会自动初始化为 0

      这样一来,我们只需要,再对其进行+1的操作,就可以表示每一轮有水果进入篮子,进篮子的问题解决了,那你怎么知道现在篮子里有几种类型呢?别急,这里就要使用map自带的函数,size,,它可以直接查询现在map的大小,刚刚不是让水果进篮子了吗,现在我们查询一下cnt.size,如果size>2,就说明这个时候有新类型的水果进入了,接下来的内容我将结合代码更清晰直观地讲解,先放上代码:

     for (int right = 0; right < n; ++right) {
            ++cnt[fruits[right]];
            while (cnt.size() > 2) {
                auto it = cnt.find(fruits[left]);
                --it->second;
                if (it->second == 0) {
                    cnt.erase(it);
                }
                ++left;
            }
            ans = max(ans, right - left + 1);
        }

没学过map的读者肯定从while开始就一头雾水,因为笔者也是这样的,因此我们还是要介绍一下这里用到的一些操作: auto可以让编译器自行推断元素类型,这样我们就不用自己绞尽脑汁地去想 it 定义为什么类型,是不是方便到爆炸了,cnt.find,顾名思义,是个查找函数,可以直接找到fruits[left] 的迭代器,啊?迭代器又是什么?这里为了方便各位理解且不影响阅读,请直接理解为map的指针,那么得到了left指向的位置,是不是就得到了刚刚我们要找的,需要被剔除的水果的类型以及个数,看看学会了STL之后,操作起来有多么方便,it->second 指的是it 指向的元素的value,不难想到 it->first 应该就是关键词key了,刚刚提到,value表示的是该元素在fruit中的数量,那此时也不需要专门定义一个指针 i 去进行 left 和 right 间的遍历了,我们直接判断 it->second 是否为0就可以,当 it->second =0,说明这个类型的水果已经被剔除干净了,我们接着就可以直接使用函数erase从cnt中删除该水果,不得不令人感叹,会STL的就是不一样,最后同样++left开始新的循环就可以了,写完这篇之后,笔者要继续学习那该死的STL了,有错误的地方希望大家能够不吝赐教批评指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值