JavaScript算法02-栈排序

一、问题描述

栈排序:对栈进行排序,是的最小元素位于栈顶,最多只能使用一个其他的临时栈存放数据,但不得将元素复制到别的数据结构(如数组)中。该栈支持如下操作:push、pop、peek和isEmpty。当栈为空时,peek返回-1。
示例:
输入:
[“SortedStack”, “push”, “push”, “peek”, “pop”, “peek”]
[[], [1], [2], [], [], []]
输出:
[null,null,null,1,null,2]
说明:栈中的元素数目在[0,5000]范围内。

二、解题方法

2.1辅助栈

使用辅助栈是最容易想到的解题方法了,毕竟题目都给出明确的暗示了,下面近一步给出解题思路以及详细的代码。解题思路:

  1. 首先定义两个栈stack1和stack2(辅助栈),其中stack1用于存储升序元素,stack2用于存储辅助的降序元素,其中stack2中的元素要小于stack1中的元素;
  2. 每次元素入栈时,都需要先判断讲被压入栈的值x与升序栈顶的关系。当stack1不为空时,若x大于栈顶元素,则将该栈顶元素压入stack2中,直至stack1栈顶元素大于x,再将x压入stack1,最后将stack2中所有元素压入stack1;
  3. 每次出栈时,将stack1正常出栈即可;
  4. 取栈顶元素和出栈操作类似。
var SortedStack = function() {
    this.S1=[];
    this.S2=[];
};

/** 
 * @param {number} val
 * @return {void}
 */
SortedStack.prototype.push = function(val) {
     //比较stack1栈顶元素与val的大小,若后者大则将stack1栈顶元素出栈到stack2中
    while(!this.isEmpty()&&this.peek()<val) {
        this.S2.push(this.S1.pop());
    }
     //将val入栈
    this.S1.push(val);
    //将stack2中的值返回到stack1
    while(this.S2.length){
        this.S1.push(this.S2.pop());
    }
};

/**
 * @return {void}
 */
SortedStack.prototype.pop = function() {
    return this.S1.pop();
};

/**
 * @return {number}
 */
SortedStack.prototype.peek = function() {
    return this.S1.length?this.S1[this.S1.length-1]:-1
};

/**
 * @return {boolean}
 */
SortedStack.prototype.isEmpty = function() {
    return !this.S1.length;
};

/**
 * Your SortedStack object will be instantiated and called as such:
 * var obj = new SortedStack()
 * obj.push(val)
 * obj.pop()
 * var param_3 = obj.peek()
 * var param_4 = obj.isEmpty()
 */

提交结果如下:
在这里插入图片描述

2.2辅助栈(改进版)

其实也不知道算不算改进,只是在处理stack2时,不在每次最后都把stcak2中的元素压入stack1,而是在和被压入栈的元素x进行比较之后,将大于x的这部分stack2中的元素,压入stack1,具体思路如下:

  1. 首先定义两个栈stack1和stack2(辅助栈),其中stack1用于存储升序元素,stack2用于存储辅助的降序元素,其中stack2中的元素要小于stack1中的元素;
  2. 每次元素入栈时,都需要先判断讲被压入栈的值x与两个栈栈顶元素的关系。当stack2不为空时,若x小于栈顶元素,则先将该栈顶元素出栈至stack1;当stack1不为空时,若x大于栈顶元素,则将该栈顶元素压入stack2中,最后将x压入stack1。
  3. 每次出栈时,若stack2不为空,我们需要将stack2中所有元素压入stack1中,由于stack2中元素为降序,则清空至stack1中依然可以保证stack1为升序,然后再将stack1正常出栈即可;
  4. 取栈顶元素和出栈操作类似。

代码部分如下:

var SortedStack = function() {
    this.sortStack1=[];
    this.sortStack2=[];
};

/** 
 * @param {number} val
 * @return {void}
 */
SortedStack.prototype.push = function(val) {
    //比较stack2栈顶元素与val的大小,若前者大则将stack2栈顶元素出栈到stack1中
    while(this.sortStack2.length&&this.sortStack2.[this.sortStack2.lehgth-1]>val){
        this.sortStack1.push(this.sortStack2.pop());
    }
     //比较stack1栈顶元素与val的大小,若后者大则将stack1栈顶元素出栈到stack2中
    while(!this.isEmpty()&&this.peek()<val) {
        this.sortStack2.push(this.sortStack1.pop());
    }
     //将val入栈
    this.sortStack1.push(val);
    
};

/**
 * @return {void}
 */
SortedStack.prototype.pop = function() {
    //将stack2清空至stack1,stack1仍为升序
    while(this.sortStack2.length){
        this.sortStack1.push(this.sortStack2.pop());
    }
    if(!this.isEmpty())
    return this.sortStack1.pop();
};

/**
 * @return {number}
 */
SortedStack.prototype.peek = function() {
    //将stack2清空至stack1中,stack1中仍为升序
    while(this.sortStack2.length){
        this.sortStack1.push(this.sortStack2.pop());
    }
    return this.sortStack1.length?this.sortStack1[this.sortStack1.length-1]:-1
};

/**
 * @return {boolean}
 */
SortedStack.prototype.isEmpty = function() {
    return !this.sortStack1.length;
};

/**
 * Your SortedStack object will be instantiated and called as such:
 * var obj = new SortedStack()
 * obj.push(val)
 * obj.pop()
 * var param_3 = obj.peek()
 * var param_4 = obj.isEmpty()
 */

此代码可以通过测试用例,但在最后提交时却显示超出时间限制,优化失败。
在这里插入图片描述但是很奇怪,后面我去看了提交方法里面,发现有一个方法的思想和我上面的思想基本一致,他们把这个命名为惰性优化,然后提交之后可以通过,并且时间还比2.1少。我就不理解了,为啥呢???!!!!下面先贴出人家的代码:

var SortedStack = function() {
    this.temp = [];
    this.stack = [];
};

/** 
 * @param {number} val
 * @return {void}
 */
SortedStack.prototype.push = function(val) {
    // 惰性更新,每次元素放到temp之后先不放回
    // 因此每次更新先把temp中大于val的拿回来
    while(this.temp.length && this.temp[this.temp.length - 1] > val){
        this.stack.push(this.temp.pop());
    }
    // 然后再看stack栈顶的情况
    // 一般来说,只要执行了上面那个,这个循环就不用执行了,因为栈顶已经大于val了;
    while(this.stack.length && this.stack[this.stack.length - 1] < val){
        this.temp.push(this.stack.pop());
    }
    this.stack.push(val);
};

/**
 * @return {void}
 */
SortedStack.prototype.pop = function() {
    while(this.temp.length){
        this.stack.push(this.temp.pop());
    }
    return this.stack.pop();
};

/**
 * @return {number}
 */
SortedStack.prototype.peek = function() {
    while(this.temp.length){
        this.stack.push(this.temp.pop());
    }
    return this.stack[this.stack.length - 1] || -1;
};

/**
 * @return {boolean}
 */
SortedStack.prototype.isEmpty = function() {
    return this.stack.length === 0 && this.temp.length === 0
};

/**
 * Your SortedStack object will be instantiated and called as such:
 * var obj = new SortedStack()
 * obj.push(val)
 * obj.pop()
 * var param_3 = obj.peek()
 * var param_4 = obj.isEmpty()
 */

2.3 二分法

上面优化想的太简单了,是从人工的角度去考虑的,而不是从计算机的角度去考虑的,所以赶紧去拜读了别人提交的方法。这个二分法是提交用例中用时最短的,其整体思路如下:

  • 在压入元素val时,对栈stack,如果栈为空,则直接压入元素;
  • 否则,初始化两个变量left、right分别记住栈低和栈顶元素,当left<right时,初始化变量m记住中间位置,要是stack1[m]>val,则第一个大于val的值落在m-right中,令left=m+1;否则第一个大于val的值落在left到m中,令right=m-1(这一步本质上就是利用二分法的思想找到第一个大于val的值,并在其后插入val,保证栈中元素是升序的);
var SortedStack = function() {
    this.s1=[];
};

/** 
 * @param {number} val
 * @return {void}
 */
SortedStack.prototype.push = function(val) {
    //栈为空,则直接入栈
    if(this.isEmpty()){
        this.s1.push(val);
    }else{
        let left=0;
        let right=this.s1.length-1;
        while(left<=right){
            let m=(right+left)>>1;
            if(this.s1[m]>val){
                left=m+1;
            }else{
                right=m-1;
            }
        }
        this.s1.splice(right+1,0,val);
    }
};

/**
 * @return {void}
 */
SortedStack.prototype.pop = function() {
    return this.s1.pop();
};

/**
 * @return {number}
 */
SortedStack.prototype.peek = function() {
    return this.s1.length?this.s1[this.s1.length-1]:-1
};

/**
 * @return {boolean}
 */
SortedStack.prototype.isEmpty = function() {
    return !this.s1.length;
};

/**
 * Your SortedStack object will be instantiated and called as such:
 * var obj = new SortedStack()
 * obj.push(val)
 * obj.pop()
 * var param_3 = obj.peek()
 * var param_4 = obj.isEmpty()
 */

这里还有个小插曲,在二分法找中间位置时,首先我按照最常见的"/"对下标进行操作,结果在提交后有部分测试用例中出现了报错现象,而使用“>>1”则可无错通过。这是因为>>1得意思是二进制右移一位,数被除2不取余。所以15/2=7.5,而15>>1结果为7。

三、知识补充

3.1 JS中的splice()方法介绍

**作用:**主要原来对数组进行增、删操作,该方法可返回被删除的元素,改变原数组。
**参数:**有三个及以上参数。

  • 第一个参数是起始位置,即数组索引;
  • 第二个参数是要删除的元素个数,如果不删除元素则设置为0,若该参数是负数则默认为0;
  • 第三个即往后参数代表要添加近数组的值。

3.2 使用示例

3.2.1 只有一个参数的情况

当splice()中只存在一个参数i时,表示删除数组中索引为i及其之后的所有元素,返回删除的元素,数组原地修改。其中若i为非负整数,则删除数组i及i之后的所有元素;否则,索引由后往前计算,最后一位数的索引为-1,倒数第二位数索引为-2,依此类推。删除i及其之后的元素。

var a = [1, 2, 3, 4, 5]
a.splice(-3)
console.log(a) // [1, 2]
var a = [1, 2, 3, 4, 5]
a.splice(0) // 或 a.splice(-5)
console.log(a) // []
3.2.2 有两个参数的情况

当 splice 方法有两个参数时,两个参数必须均为整数。表示从数组中索引为 i 开始删除,一共删除 j 个元素。

var a = [1, 2, 3, 4, 5]
a.splice(0, 3)
console.log(a) // [4, 5]
var a = [1, 2, 3, 4, 5]
a.splice(1, a.length - 2)
console.log(a) // [1, 5]
var a = [1, 2, 3, 4, 5]
a.splice(-2, 2)
console.log(a) // [1, 2, 3]
3.2.3 参数为3个或以上时
a.splice(i, j, e1, e2, ...)

其中

  • i:整数,表示索引的起始位置
  • j:整数,表示删除的个数
  • e1、e2、…:删除相应元素之后要添加的元素

当 splice 方法有 3 个参数时,表示从索引为 i 位置开始删除 j 个元素,然后在从 i 位置添加 e1,e2,…,返回删除的元素,原地修改。若 j 为 0,则表示一个元素也不删除,则元素从 i 前一个位置开始插入。若 j > 0,则表示从 i 位置开始(包括 i 位置)删除 j 个元素,然后从 i 后面开始插入。

var a = [1, 2, 3, 4, 5]
a.splice(2, 1, 'aaa')
console.log(a) // [1, 2, 'aaa', 4, 5]
var a = [1, 2, 3, 4, 5]
a.splice(1, 0, 'a', 'b', 'c')
console.log(a) 
// [1, 'a', 'b', 'c', 2, 3, 4, 5]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值