代码随想录算法训练营第十三天 | 239. 滑动窗口最大值、347. 前 K 个高频元素

239. 滑动窗口最大值

视频讲解

主要思路:

要找每个滑动窗口中最大值,所以单调队列里只需要维护可能成为最大值的数列就行,同时保证为非递增排列

易错点:

(1)pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作,因为该算法逻辑就是让最大值放在单调队列出口处,而若单调队列出口处与滑动窗口出口处元素相等,说明滑动窗口已经滑动到最大值处了

(2)push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止

(3)使用的数据结构为deque

代码实现:

class Solution {
private:
    class MyQueue {
    public:
        deque<int>que;
        void myPop(int value) {
            //访问que前均要先判断非空
            if(!que.empty() && que.front() == value) que.pop_front();    
        }
        //相对于普通的push多了一个判断弹出前面元素的操作
        void myPush(int value) {
            while(!que.empty() && value > que.back()) que.pop_back();
            que.push_back(value);
        }
        int findMax() {
            return que.front();
        }
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int>ans;
        MyQueue que;
        //先处理前k个元素,注意不要忘了返回这前k个元素的最大值
        for(int i = 0; i < k; i++) {
            que.myPush(nums[i]);
        }
        ans.push_back(que.findMax());
        //滑动窗口是一位一位往后移动的
        /*这段代码的逻辑是先弹出滑动窗口中第一个元素,
         如果它在单调队列中,由前面函数设计可知其必为最大值,如果不是就不执行;
         接着将一个新元素压入单调队列中,即滑动窗口向后移动一位;
         最后获取当前单调队列里的最大值*/
        for(int i = k; i < nums.size(); i++) {  
            que.myPop(nums[i - k]);
            que.myPush(nums[i]);    
            ans.push_back(que.findMax());
        }
        return ans;
    }
};

第一次写错误 

在获取最开始的前k个元素是忘了获取他们的最大值了

补充

(1)deque 

deque是一种双端队列(double-ended queue)数据结构,支持在队列的头部和尾部进行快速的插入和删除操作。定义在头文件deque中

deque是由一段段定长的连续存储空间组成的,每一段存储空间被称为一个缓冲区(buffer),缓冲区之间通过指针进行连接。因此,deque既可以支持在队首和队尾进行插入和删除操作,又可以支持在队列中间进行随机访问,比起其他容器,deque在插入和删除操作上的效率更高。

push_front、push_back、pop_front、pop_back、front、back等,可以用于在deque的头部或尾部插入或删除元素,或者获取deque的头部或尾部元素等

(2)什么时候能用num.size()

C++中,num.size()是一个用于获取容器中元素个数的函数。对于支持size()函数的容器类型,比如vector、string、deque等,可以使用size()函数获取容器中元素的个数。而对于其他类型的变量,如数组或指针,是无法使用size()函数获取其元素个数的。示例如下

  1. vector<int> num; // 可以使用num.size()
  2. string str = "Hello, World!"; // 可以使用str.size()
  3. int arr[5] = {1, 2, 3, 4, 5}; // 无法使用arr.size()
  4. int* ptr = arr; // 无法使用ptr.size() 

(3)class

在C++中,class是一种用于定义对象的数据类型的关键字。它可以包含成员变量和成员函数,用来描述对象的属性和行为。使用class定义的类型,可以像内置类型一样使用,也可以像其他自定义类型一样进行封装和抽象,从而实现更高效、更可靠、更易用的代码。

以下是一个简单的class的例子:

class Person {
public:
    // 成员函数
    void setName(string name) {
        m_name = name;
    }

    string getName() const {
        return m_name;
    }

    void setAge(int age) {
        m_age = age;
    }

    int getAge() const {
        return m_age;
    }

private:
    // 成员变量
    string m_name;
    int m_age;
};

在上面的例子中,我们定义了一个名为Person的class,它有两个私有成员变量m_name和m_age,以及四个公有成员函数setName、getName、setAge和getAge。成员变量和成员函数的访问权限是通过public、private、protected等关键字来控制的。public成员可以被任何代码访问和使用,private成员只能被同一个class内部的成员函数访问和使用,protected成员可以被同一个class以及它的派生类的成员函数访问和使用。

可以使用以下语句创建一个Person类型的对象:

Person p;

然后可以使用成员函数来设置和获取对象的属性:

p.setName("Tom");
p.setAge(20);
cout << p.getName() << ", " << p.getAge() << endl;

输出结果为:

Tom, 20

347. 前 K 个高频元素

视频讲解

主要思路:

(1)首先遍历原数组,统计元素出现的频率,这一类的问题可以使用map来进行统计。map的键里储存该元素,值里储存该元素出现的次数

(2)再用一个小顶堆存储前k个出现频率最高的元素

(3)倒序将小顶堆中元素存储到结果数组里

易错点:

(1)设计小顶堆

 bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;
        }

lhs.second > rhs.second,如果结果不成立,就不交换两者的相对位置;如果成立,就交换。然后代入一个最简单的冒泡排序算法场景,

lhs.secondrhs.second 进行比较,返回的是 lhs.second > rhs.second,即如果 lhs.second 大于 rhs.second,那么 lhs 的优先级就比 rhs 的优先级高,那么优先级高的反而排在后面

代码实现:

class Solution {
//加private声明为这个myComparison的class只能在Solution中使用
private:
    // 自定义设计小顶堆
    class myComparison {
    //加public声明这个myComparison的class中的operator可以在Solution中使用
    public:
        bool operator()(const pair<int, int>&lhs, const pair<int, int>&rhs) {
            return lhs.second > rhs.second;
        }
    };
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 遍历数组,统计次数
        unordered_map<int,int>map;
        for(int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }
        // 利用优先级队列定义一个大小为k的小顶堆,但这个k要在之后构造时才能体现
        priority_queue<pair<int, int>, vector<pair<int, int>>, myComparison> pri_que;
        /*利用小顶堆扫描map,虽然遍历map是按键从小到大遍历,但当将其放到pri_que中时是按
         小顶堆的方式放入*/
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
            pri_que.push(*it);
            if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                pri_que.pop();
            }
        }
        // 在小顶堆中父节点是最小的,而题目要求返回顺序从大到小,所以先把遍历的元素从后往前排
        vector<int> ans(k);
        for(int i = k - 1; i >= 0; i--) {
            /*pri_que.top() 返回的是一个 pair<int, int> 类型的对象,
            而不是一个 int 类型的整数,必须用pri_que.top().first即返回键*/
            ans[i] = pri_que.top().first;
            pri_que.pop();
        }
        return ans;
    }
};

补充

(1)const pair<int, int>&lhs

加上 & 表示函数将会接收传递进来的参数的地址,而不是参数的值本身。

(2)priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;

定义了一个名为 pri_que 的优先队列,

第一个参数是其中其中存储的元素类型,本题是 pair<int, int>,也就是由两个 int 值组成的键值对。同时,

第二个参数指明该队列使用的作为底层存储结构的容器,为 vector<pair<int, int>> 

第三个参数指明使用的用来定义队列中元素排序方式的比较器类,为自定义的 mycomparison

(3)unordered_map<int, int>::iterator it = map.begin()

初始化一个迭代器 it,它指向 unordered_map<int, int> 类型的容器 map 的第一个键值对(即 begin() 函数返回的迭代器)。因为 map 是一个关联容器,它的键值对是按照键的大小排序的,因此 it 指向的键值对是键最小的键值对。

(4)pri_que.push(*it);

C++ 中,unordered_map 的迭代器 it 指向一个键值对(即 pair<int, int> 类型),其中 first 表示键,second 表示值。因此,*it 表示解引用这个迭代器,即得到这个键值对。在这个例子中,我们要将键值对放入小顶堆中,因此要使用 *it

第二十二天的算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二天的算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口算法的理解和应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值