码迷,mamicode.com
首页 > 其他好文 > 详细

01 栈与队列

时间:2021-03-01 13:51:59      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:rom   之间   资料   practice   设计   关系   获取元素   转化   索引   

第一章 栈与队列

关键词:滑动窗口

1-1 设计getMin功能的栈

思路:保存每次栈更新时的最小值,可以通过定义另外一个栈实现。

1-2 由两个栈组成的队列

思路: 队列是先进先出,栈是先进后出。入队列的时候将元素放入栈1,出队列时,如果栈2为空,将栈1的元素全部弹出压入栈2,这样栈2的弹出的元素就符合先进先出的原则了。这里要注意将栈1的元素压入栈2的时机

1-3 设计递归函数逆序一个栈

思路:

设计递归函数reverse实现栈的逆序,参数是一个栈s:

1.如果s为空,则直接返回

2.如果s非空:

  • (1)得到并删除s的栈底元素v
  • (2)调用reverse函数,参数依旧是s
  • (3)将栈底元素v压入栈s

上面的整个逻辑是完整,唯一问题就是(1)得到并删除s的栈底元素v该怎么实现?

同样设计一个递归函数:getAndRemoveLastElement实现返回并删除栈底元素,参数是栈s:

1.出栈一个元素v

2.检查栈是否为空:

  • 为空,此时v就是栈底元素,因此返回v

  • 非空,则调用getAndRemoveLastElement获取元素t,此时t为栈底元素而v不是,因此将v再压入栈,返回t。

1-5 用一个栈实现另外栈的元素排序

思路:

假设排序栈为orderStack,另外一个栈为helpStack,实现orderStack从顶到底按从大到小的顺序排序,这里可以转换一下思路,等同于实现helpStack从顶到底按从小到大的顺序排序。

  • 将orderStack的元素top1依次出栈,与helpStack栈顶元素top2进行比较,如果top1 < top2,直接入栈,
  • 否则,将helpStack的元素不断出栈(出栈的元素可以暂时放入orderStack保存),直到满足top1<top2。然后再将之前出栈的元素入栈。

注意点:

  • 出栈以及查看栈顶元素需要判断当前栈是否为空(良好的习惯)
1-7 生成窗口最大值数组

思路:

? 需要借助双端队列。队列用于保存当前滑动窗口下的最大值的下标

需要注意:

  • 队列中丢弃过期下标
  • 确定当前窗口最大值的下标。
  • 上面二者的顺序也需要注意

每个下标的都至多进出队列一次,所以时间复杂度时O(N)。

个人注意点:

  • 使用C++ deque将deque.end()方法等同于deque.back(),常识性错误,不要把迭代器与指针完全等同
1-9 求最大子矩阵(牛客链接)

力扣链接

技术图片

第一种思路个人理解(多个横着的柱状图矩形面积求最大):

1.首先将原问题问题进行分解。

  • 定义子问题:以(i,j)为右下角的全为1的最大子矩形面积。
  • 设矩阵的形状是(m,n),那么一共有m*n个上述的子问题,原问题的求解结果 = 子问题求解结果的最大值。

2.求解每个子问题

  • 计算出矩阵的每个元素的左边连续 1 的数量

  • 采用柱状图求最大面积的思想,详细可参照力扣链接。

    • 下图中自下往上,每个位置算一次面积,保留最大面积。

技术图片

时间复杂度=子问题的个数*子问题的求解时间 = O(nmn)

另外一种的理解(多个竖着的柱状图的矩形面积求最大)

1.首先对问题进行分解,转化成求以当前行为底的直方图的最大矩形面积,有N行则操作N次。

2.求直方图的的最大矩形面积时,对每个小矩形向左和右扩展。实际计算需要采用栈,栈从栈顶到栈底存有每个小矩形的位置下标。要求从顶到底高度递减。有M列,即M个位置,要求O(M)操作完成。

  • 入栈的规则,高度比较高的小矩形的位置下标入栈。
  • 出栈规则,出现高度比栈顶的矩形高度小的矩形,不断出栈,直到符合2定义的规则。这里出栈需要对出栈的矩形进行扩展,计算其最大矩形面积。
  • 遍历完数组后,可能栈中还有元素,这是需要另外处理。

总结:

  • 上面2个思路本质上是一样的,唯一的区别就是求柱状图面积那里一个采用栈,一个没有用栈
    • 没有用栈需要每个点都计算一次面积,自己记录当前矩形的高度与宽度(更加简洁)
    • 使用栈可以少计算几次矩形面积
1-10 最大值减去最小值小于等于n的子数组数量

基本思路:

首先暴力解法时找出所有的子数组然后判断是否满足条件,时间复杂度时O(N^3)。

基本步骤

? 按照数组的开头的位置不同,分别计算从位置0开始的数组,从位置1开始的数组...的满足条件的子数组个数,然后相加即可。

注意:

  • 需要借助1-7的滑动窗口的思想,使用2个双端队列用于维护当前滑动窗口下的最大值与最小值。
  • 本题目还需要一些常识性的判断arr[i,j]满足最大值减去最小值小于等于n这个条件,那么其子数组都满足这个条件,反之,也是成立的。
  • 滑动窗口的两头索引只会增加,数组中所有元素都只进出队列一次,所以复杂度时O(n)
章节知识点总结:

1.滑动窗口的思想_csdn

  • 滑动窗口本质上来源于单调性,一般可以理解为,随着左端点位置的增加,其最优决策的右端点位置单调不减
  • 滑动窗口一般用于具有递增性质的字符串或者数组问题,因为滑动窗口是双指针的特殊场景,由于具有递增性质,所以使用滑动窗口可以 “聪明的”枚举出 所有可能满足题意子串或者子数组

2 栈的使用

  • 通常元素之间有很明显的大小关系,可能考虑用栈。

补充题目:

可见山峰对问题(代码未通过)

一个环形山脉,哪两座山的烽火可以相互看见,条件如下:

  • (1)相邻的山;
  • (2)两座山中的某条路 中间不存在比这两座山的最小值大的山(可以相等);

思路:

简化问题:n座山的高度都是不同的情况:

  1. 如果只有1座山,那么有0对
  2. 如果有两座山,那么有1对。
  3. 如果有n座山,那么有2*n-3对。

假设A(1),B(2),C(3),D(4)三座山,A最多找到2座山配对,B最多也能2座山配对。我们要确保,A的2个配对与B的2个配对不能重复的情况。例如(A,B)可能会重复计数2次。因此我们需要在配对的时候,进行约定,避免这种重复情况出现。

限制:对于每座山,只配对比它高的山峰(小找大)。

上面的限制只是为了方便思考,不会对最终的结果产生思想,但是解题的时候有了上述的限制,对解题思路的产生有着重大的作用。对这个解题空间进行了明确的划分。

在这样的限制下,考虑n座不同高度山的情况,我们需要考虑三种情况:

  • 情况1:最高峰配对数目:0

  • 情况2:次高峰配对数目:1 (由于只能与比自己高的山配对,因此只有与最高峰配对这一种情况)

  • 情况3:剩下的山峰:(n-2)*2 (剩下的每座山峰都可以从2个方向找到比其高的山峰配对)

n座山中的高度可以重复:

在这种情况下,我们需要借助栈去解决问题,借助栈的原因在于小找大的限制。

基本思路如下:

1)先挑选一个高度最高的山入栈。

2)然后从高度最高的山下一座山开始遍历,完成这个遍历。

  • 如果下座山小于栈顶的山,则入栈
  • 如果下一座山高于栈顶的山,那么不断出栈,直到栈顶的山高度大于下一座山
    • 对于每一个出栈的山,计算其配对数目为 山的个数*2+山的个数为2的组合数

3)遍历完成后,堆栈中还会剩余。对于剩余的也要分为2种情况考虑:

  • 首先如果栈的大小还>=3,那么不断出栈,并累加配对数目,其计算方式与步骤2一致
    • 情况1:栈的大小为2(最大值与次大值的情况)
      • 最高山的数目为1座,则 山的个数*2+山的个数为2的组合数
    • 情况2:栈的大小为1
#include<iostream>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;
typedef struct moun{
    int h;
    int num;
} mu,*pmu;

int combination_sum(int n){
    return (n)*(n-1)/2;
}

vector<mu> m(1000005);
stack<mu> mystack;
int main(){
    int num;
    cin >> num;
    int max_index = 0;
    int max_h = -100000000;
    for(int i = 0;i < num;++i){
        cin  >> m[i].h;
        m[i].num = 1;  
        if(m[i].h > max_h){
            max_h = m[i].h;
            max_index = i;
        }
      
    }
    int result = 0;
    int index = 0;
    pmu p_top;
    mystack.push(m[max_index]);
    for(int j = 1;j < num;++j){
        index = (j+max_index)%num;
        p_top = &mystack.top();
        if(p_top->h > m[index].h){      //栈顶的山高度大于下一座山,将山入栈
            mystack.push(m[index]);
        }else if(p_top->h == m[index].h){  //栈顶的山高度等于下一座山,栈顶山数目累加
            p_top->num += 1;
        }else{               //栈顶的山高度小于下一座山,栈顶山出栈,计算出栈的山配对数。
            result +=  combination_sum(p_top->num) + p_top->num*2;
            mystack.pop();        //这边需要多次处理,可能需要多次操作
            mystack.push(m[index]);
        }    
    }
    if(!mystack.empty()){
        while(mystack.size() >= 3){           #遍历后还有超过2座山的部分,继续之前处理
             p_top = &mystack.top();
             result +=  combination_sum(p_top->num) + p_top->num*2;
             mystack.pop();  
        }
        if(mystack.size() == 2){             # 还剩2座山
            pmu first,second;
            first = &mystack.top();
            mystack.pop(); 
            second = &mystack.top();
            mystack.pop(); 
            if(second->num == 1){            # 最底层山的数目为1
                result +=  combination_sum(first->num) + first->num;
            }else{                           # 最底层山的数目大于等于2
                result +=  combination_sum(first->num) + first->num*2;
                result +=  combination_sum(second->num);
            }
        }else if(mystack.size() == 1){      # 只有最后一座山
             p_top = &mystack.top();
             result +=  combination_sum(p_top->num);
             mystack.pop();  
        } 
    }
    cout << result << endl;
    return 0;
}

参考资料

01 京东2017校招编程题 - 保卫方案(山峰对数量)
02 程序员代码面试指南


20210201

01 栈与队列

标签:rom   之间   资料   practice   设计   关系   获取元素   转化   索引   

原文地址:https://www.cnblogs.com/kfcuj/p/14459492.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!