剑指offer之栈、堆、队列

1.用两个栈实现队列

题目:https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof

思路:

加入队尾 appendTail()函数: 将数字 val 加入栈 A 即可。
删除队首deleteHead()函数: 有以下三种情况。

  • 当栈 B 不为空: B中仍有已完成倒序的元素,因此直接返回 B 的栈顶元素。
  • 否则,当 A 为空: 即两个栈都为空,无元素,因此返回 -1−1 。
  • 否则: 将栈 A 元素全部转移至栈 B 中,实现元素倒序,并返回栈 B 的栈顶元素。
class CQueue {
public:
    stack<int> s1;
    stack<int> s2;
    CQueue() {

    }
    
    void appendTail(int value) {
        s1.push(value);
    }
    
    int deleteHead() {
        int res;
        if(!s2.empty()){
            res=s2.top();
            s2.pop();
        }
        else{
            if(s1.empty())  return -1;
            while(!s1.empty()){
                s2.push(s1.top());
                s1.pop();
            }
            res=s2.top();
            s2.pop();
        }
        return res;
    }
};

2.包含Min函数最小的栈

题目:https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof

思路:

 将 min() 函数复杂度降为 O(1) ,可通过建立辅助栈实现;

  • 数据栈 A : 栈 A 用于存储所有元素,保证入栈 push() 函数、出栈 pop() 函数、获取栈顶 top() 函数的正常逻辑。
  • 辅助栈 B : 栈 B 中存储栈 A 中所有 非严格降序 的元素,则栈 A 中的最小元素始终对应栈 B 的栈顶元素,即 min() 函数只需返回栈 B 的栈顶元素即可。
class MinStack {
public:
        stack<int> s1;
        stack<int> s2;
    /** initialize your data structure here. */
    MinStack() {

    }
    
    void push(int x) {
        s1.push(x);
        if(s2.empty() || s2.top() >= x){
            s2.push(x);
        }
    }
    
    void pop() {
        int tmp=s1.top();
        s1.pop();
        if(tmp==s2.top()){
            s2.pop();
        }
    }
    
    int top() {
     return s1.top();
    }
    
    int min() {
        return s2.top();
    }
};

3.最小的k个数

题目:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof

思路:

(1)堆排序   

//借助priority_queue
class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        vector<int> res;
        if(k==0)  return res;
        priority_queue<int> bigHeap;
        for(int i=0; i<k; i++)  bigHeap.push(arr[i]);
        for(int i=k; i<arr.size(); i++){
            if(arr[i]<bigHeap.top()){
                bigHeap.pop();
                bigHeap.push(arr[i]);
            }
        }
    
        for(int i=0; i<k; i++){
            res.push_back(bigHeap.top());
            bigHeap.pop();
        }
        return res;
    }
};

(2)快速选择排序(快排变种)

        我们的目的是寻找最小的 k 个数。假设经过一次 partition 操作,枢纽元素位于下标 m,也就是说,左侧的数组有 m 个元素,是原数组中最小的 m 个数。那么:

  • 若 k = m,我们就找到了最小的 k 个数,就是左侧的数组;
  • 若 k<m ,则最小的 k 个数一定都在左侧数组中,我们只需要对左侧数组递归地 parition 即可;
  • 若 k>m,则左侧数组中的 m 个数都属于最小的 k 个数,我们还需要在右侧数组中寻找最小的 k-m 个数,对右侧数组递归地 partition 即可。
//快速选择
class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        int len=arr.size();
        int start=0, end=len-1;
        if(len<k || k<=0 || len<=0)  return {};
        int idx=quickSort(arr, start, end);
        while(idx!=k-1){
            if(idx > k-1)  end=idx-1;
            if(idx < k-1)  start=idx+1;
            idx=quickSort(arr, start, end);
        }
        return vector<int>(arr.begin(), arr.begin()+k);
    }
    int quickSort(vector<int>& arr, int l, int r){
        int pivot=arr[l];
        int i=l,j=r;
        while(i<j){
            while(i<j && arr[j]>=pivot) j--;
            while(i<j && arr[i]<=pivot) i++;
            if(i<j){
                int tmp=arr[i];
                arr[i]=arr[j];
                arr[j]=tmp;
            }
        }
        arr[l]=arr[i];
        arr[i]=pivot;
        return i;
    }
};

快速选择算法的几点局限性:

第一,算法需要修改原数组,如果原数组不能修改的话,还需要拷贝一份数组,空间复杂度就上去了。

第二,算法需要保存所有的数据。如果把数据看成输入流的话,使用堆的方法是来一个处理一个,不需要保存数据,只需要保存 k 个元素的最大堆。而快速选择的方法需要先保存下来所有的数据,再运行算法。当数据量非常大的时候,甚至内存都放不下的时候,就麻烦了。所以当数据量大的时候还是用基于堆的方法比较好。

4.滑动窗口的最大值

题目:https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof

思路:

  • 遍历数组,将存放在双向队列中,并用L,R来标记窗口的左边界和右边界。
  • 队列中保存的并不是真的数,而是该数值对应的数组下标位置,并且数组中的数要从大到小排序。
  • 如果当前遍历的数比队尾的值大,则需要弹出队尾值,直到队列重新满足从大到小的要求。
  • 刚开始遍历时,L和R都为0,有一个形成窗口的过程,此过程没有最大值,L不动,R向右移。
  • 当窗口大小形成时,L和R一起向右移,每次移动时,判断队首的值的数组下标是否在[L,R]中,如果不在则需要弹出队首的值,当前窗口的最大值即为队首的数。
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        deque<int> tmp;
        if(nums.empty() || nums.size()<k){
            return res;
        }
        //遍历nums
        for(int i=0; i<nums.size(); i++){
            //保证从大到小,如果前面小,就pop,直至满足条件
            while(!tmp.empty() && nums[i]>=nums[tmp.back()]){
                tmp.pop_back();
            }
            
            //判断当前队首是否有效
            if(!tmp.empty() && tmp.front()<=i-k){
                tmp.pop_front();
            }

            //添加当前值对应下标
            tmp.push_back(i);

            //当窗口长度为k时,保存当前窗口中最大值
            if(i+1>=k){
                res.push_back(nums[tmp.front()]);
            }
        }
        return res;
    }
};

上题包含Min函数最小的栈,其使用 单调栈 实现了随意入栈、出栈情况下的 O(1) 时间获取 “栈内最小值” 。本题同理,不同点在于 “出栈操作” 删除的是 “列表尾部元素” ,而 “窗口滑动” 删除的是 “列表首部元素” 。

窗口对应的数据结构为 双端队列 ,本题使用 单调队列 即可解决以上问题。

5.队列的最大值

题目:https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof

思路:和上题思路基本一样

参考https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/solution/mian-shi-ti-59-ii-dui-lie-de-zui-da-zhi-by-leetcod/

class MaxQueue {
public:
    deque<int> res;
    queue<int> q;
    MaxQueue() {

    }
    
    int max_value() {
        if(res.empty()) return -1;
        return res.front();
    }
    
    void push_back(int value) {
        while(!res.empty() && res.back()<value){
            res.pop_back();
        }
        res.push_back(value);
        q.push(value);
    }
    
    int pop_front() {
        if(q.empty()) return -1;
        int ans=q.front();
        if(ans==res.front()){
            res.pop_front();
        }
        q.pop();
        return ans;
    }
};

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值