(一)数据结构-栈

198 阅读6分钟

按照分类对剑指offer和leetcode题目汇总,并通过js实现

1.设计一个栈具有获取最小值的函数,pop,push,min,top

  • 设计思路 采用两个栈的方式,一个栈用于存储数据,另一个栈用于存储最小值
function StackMin(){
	this.stack1 = [];//存放数据
	this.stack2 = [];//存放最值
}
StackMin.prototype.push = function(data){
	this.stack1.push(data);
	if(this.stack2.length==0){
		this.stack2.push(data)
	}
	this.stack.push(data>this.min()?this.min():data)
}
StackMin.prototype.pop = function (){
	if(this.stack1.length!=0)
	{
		this.stack2.pop();
		return this.stack1.pop()
	}
	return null
}
StackMin.prototype.top = function(){
	return this.stack1.length>0&&this.stack1[this.stack1.length-1]
}
StackMin.prototype.min = function(){
	return this.stack2.length>0&&this.stack2[this.stack2.length-1]
}

2.两个栈实现队列

  • 设计思路
    • 设计两个栈,一个用于进栈,一个用于出栈
    • 进栈:直接push
    • 出栈:需要先判断出栈的栈是不是为空,在加入进栈的栈中的内容

image.png

function TwoStackToQueue() {
  this.stackIn = [];
  this.stackOut = [];
}
//通过在原型中添加属性和方法的方式去实现队列的方式
TwoStackToQueue.prototype.enqueue = function (data) {
  //进队列的时候直接进
  this.stackIn.push(data)
}
//出队列的时候借助另外的栈暂存并调整顺序
TwoStackToQueue.prototype.dequeue = function () {
  if (this.stackOut.length == 0) {
    while (this.stackIn.length) {
      this.stackOut.push(this.stackIn.pop())
    }
  }
  return this.stackOut.pop() || null
}

3.两个队列实现栈

通过两个队列实现栈:

  • 两个队列哪个为空哪个入队,入队之后从头去取另一个队列中的元素
  • 出队的时候从头部出,(顺序调换之后,先进就后出)

image.png

function TwoQueueToStack() {
  this.queue1 = [];
  this.queue2 = [];
}
TwoQueueToStack.prototype.push = function (data) {
  if (this.queue1.length == 0) {
    this.queue1.push(data);
    //另外的队列从头取并插入到当前的队列
    while (this.queue2.length) {
      this.queue1.push(this.queue2.shift());//shift()从头部删除元素,改变当前数组
    }
  }
  if (this.queue2.length == 0) {
    this.queue2.push(data);
    while (this.queue1.length) {
      this.queue2.push(this.queue1.shift())
    }
  }
}
​
TwoQueueToStack.prototype.pop = function () {
  if (this.queue1.length) return this.queue1.shift()
  if (this.queue2.length) return this.queue2.shift()
}

4 使用递归函数和栈实现栈的逆序操作

eg:

入栈顺序:1,2,3

逆序之后栈内元素:3,2,1

思路: (通过两个递归函数实现)

  • 实现递归函数:删除栈底元素并返回getRemoveBottom(stack),
  • 实现递归逆序(递归获取每次的返回元素,在进行入栈)reverse(stack)
/*
使用递归函数和栈实现将栈逆序:
入栈顺序:1,2,3
逆序之后栈内元素:3,2,1
思路:
- 实现递归函数:删除栈底元素并返回删除
- 实现递归逆序
  - 递归获取每次的返回元素,在进行入栈
*/
function getRemoveBottom(stack) {
  var result = stack.pop();
  if (stack.length == 0) {
    return result;//返回递归最后的元素,也就是栈底元素
  } else {
    var last = getRemoveBottom(stack);//
    //递归结束将值push到stack中
    stack.push(result)//先返回了2push进去右返回了3push进去,每次操作只是删除了栈底元素
    return last//返回的始终都是栈底元素
  }
}
/*
第二个递归实现逆序,递归每次删除返回的栈元素直到拿到栈顶元素在push到栈中
*/
function reverse(stack) {
  if (stack.length == 0) return
  var i = getRemoveBottom(stack);
  reverse(stack);//将栈取空之后,在将返回的栈底元素依次入栈
  stack.push(i)
}
var stack = [1, 2, 3, 4, 5];
reverse(stack)
console.log(stack);

5 滑动窗口的最大值(采用单调栈)

可以解决窗口内最大值和最小值的解决

题目

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。 返回滑动窗口最大值。

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 
  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

思路

  • 设置一个双端队列(存储的是滑动窗口的下标),

    • 当前值和双端队列中的最后一个值比较,(单调栈)

      • 当前值大于,表明该值具有最晚的过期时间,依次遍历窗口中的元素,从队尾pop出比当前值小的下标,并将自己的下标入队.这里保证了滑动窗口中存放的第一个元素下标值始终是该滑动窗口中的最值下标
      • 当前值小,则直接进队列
  • 判断窗口中的元素的过期时间,window[0]==i-w表明当前值过期,从头部出队列

  • 当i>k-1表示滑动窗口已经满足k值,此时获取window中的第一个元素下标对应的数值存入res,作为当前窗口的最值

  • 结束返回res

时间复杂度

  • 这里的将所有的元素在双端队列中进行了入队和出现操作 ,时间复杂度是O(n)
  • 入队出队的时间复杂度都是O(1)

代码

function maxSlideWindow(arr,k){
    if(!arr||arr.lenghth<k||arr.length<1) return null;
    var window = [];
    var res = []
    //每个元素进行一次遍历
    for(var i = 0;i<arr.length;i++){
        if(window[0]==i-k){
            window.shift();//头部元素出窗口
        }
        var j = window.length-1
        while(j>=0&&arr[window[j]]<=arr[i]){
            j--;
            window.pop()
        }
        window.push(i);
        if(i>=k-1){
            res.push(arr[window[0]])
        }
    }
    return res
}

单调栈的补充(可以解决类似问题)

前提:数组中的元素是不重复的,构造的栈是单调递减的栈(求的是左右两边比我大的值)

  • 单调栈:每次入栈都是比自己小的数入栈,

  • 待入栈的元素如果比栈顶元素大,说明当前数是栈顶元素的右边最近比他大的数,栈顶元素的下一个元素是左边最近比他大的数,

    • 此时需要将栈顶元素释放,释放的过程中就可以得到栈顶元素左右两边离他最近的比他大的数
    • 出栈之后依次和下一个栈顶元素比较,如果栈为空此时当前带入栈的元素入栈
    • 所有元素遍历完成之后,栈中的元素出栈

image.png 注意这里存储的是数组中的下标

6 构造数组的MaxTree

将数组构造成一棵二叉树(数组中没有重复的数字,最大节点都是树的头),要求时间复杂度为O(N),空间复杂度为O(N)

思路

  • 方式1:大顶堆解决

  • 方式2:这里可以采用单调栈的方式获取左右离他最近的比他大的数来创建最大树

    • 建树的规则

      • 每一个树的父节点是左边最近的离他最大的数和右边最近的离他最大的数中的最小值
      • 如果一个数左边和右边都没有离他最近的数,说明当前数是整个数组的最大值,这个树是maxTree的最大值

image.png - 解题思路

    -   先遍历所有的数组将数组中的数字转化为节点保存在node节点数组中
    -   声明单调栈,通过入栈出栈来保存每个元素对应的元素,并将他们以对象的方式存入parent中
    -   结束后栈不为空将栈中剩余的元素弹出
    -   根据建树的规则依次遍历每个节点,将父子元素之间连接起来构成最大二叉树并返回

    **注意:在刷题过程中哈希表的增删查找都是O(1)**
//通过单调栈构建
function Node(data) {
  this.value = data;
  this.left = null
  this.right = null
}
function MaxTree(arr) {
  //将arr中的数字变为node节点
  var node = []
  for (var i = 0; i < arr.length; i++) {
    node[i] = new Node(arr[i])
  }
  //声明单调栈
  var stack = []
  //声明一个map.保存每一个节点的父节点
  var parents = {}
  //遍历所有的node数组中的节点,进栈出栈寻找最近的值
  for (var i = 0; i < node.length; i++) {
    var j = stack.length - 1
    while (j != 0 && stack[j].value < node[i].value) {//单调栈出栈
      //出栈并保存每个节点的父节点
      parents[stack.pop()] = (j == 0 || node[i].value < stack[--j].value) ? node[i] : stack[j];
    }
    stack.push(node[i])
  }
  //遍历结束栈中的元素弹出
  while (stack.length != 0) {
    parents[stack.pop()] = (stack.length != 0 ? stack[stack.length - 1] : null)
  }
  //构建树(通过建树的规则)
  var head = null;
  var parent = null;
  for (var i = 0; i != arr.length; i++) {
    parent = parents[node[i]];
    if (parent == null) {
      head = node[i]
    } else if (parent.left == null) {
      parent.left = node[i]
    } else {
      parent.right = node[i]
    }
  }
  return head
}