算法周记-栈

本周,重温算法中关于单链表的内容,主要涉及:

  1. 用Swift实现了单/双链表
  2. 用C实现单链表的一些常见操作
  3. 刷 Leecode

Swift 实现栈 Stack

栈是很常见的数据结构,下面是Swift实现的 class版:

class ZZHStack<T> {

    private var array = [T]()
    var size: Int {
        get {
            return self.array.count
        }
    }

    var isEmpty: Bool {
        get {
            return self.array.count == 0
        }
    }
    func push(value: T) {
        self.array.append(value)
    }

    func pop() -> T? {
        let last = self.array.removeLast()
        return last
    }
    func peek() -> T? {
        return array.last
    }

}

Leecode 相关的题

844 Backspace String Compare

比较含退格的字符串,难度:简单。
思路:使用栈,当遇到"#"将栈 pop即可。

20 Valid Parentheses

有效的括号,难度:简单。 本题使用了 C 解答

bool isValid(char *s){

    if(s == NULL){
        return false;
    }
    int length = strlen(s);
    if (length == 0) {
        return true;
    }
    if (length % 2 != 0 ) {
        return false;
    }
    char *stack = (char *)malloc(sizeof(char) * length);
    memset(stack, 0, length/2);
    char current;
    int index = 0;
    for (int i = 0; i < length; i++) {
        current = s[i];
        if (current == '('
            ||current == '{'
            ||current == '['){
                stack[index] = current;
                index ++;
            }else {
                if (strlen(stack) == 0) {
                    return false;
                }
                char last = stack[index - 1];
                if ((last == '(' && current == ')')
                    ||(last == '[' && current == ']')
                    ||(last == '{' && current == '}')) {
                    stack[index - 1] = '\0';
                    index --;
                }
            }//end if
    }//end for
    return strlen(stack) == 0;
}

155 Min Stack

最小栈,难度:简单。 思路:因为获取最小元素时要 O(1),内部要使用两个数组,一个用于存放栈的实际元素,一个用于存放当前情况下的最小元素。需要在 push 和 pop 的时候,思考最小元素的数组如何更新。

class MinStack {
    private var array = [Int]()
    private var minArray = [Int]()
    /** initialize your data structure here. */
    init() {

    }

    func push(_ x: Int) {
        array.append(x)
        if minArray.last == nil || minArray.last! >= x {
            minArray.append(x)
        }

        //print("push:\(array)\n \(minArray)")
    }

    func pop() {
        let poped = array.removeLast()
        if poped == minArray.last {
            minArray.removeLast()
        }
        //print("pop:\(array)\n \(minArray)")
    }

    func top() -> Int {
        if array.count == 0 {
            return 0
        }
        return array.last!
        return array.last ?? 0
    }

    func getMin() -> Int {
        if minArray.count == 0 {
            return 0
        }
        return minArray.last!
        return minArray.last ?? 0
    }
}

232 Implement Queue using Stacks

用栈实现队列,难度:简单。
因为栈和队列的元素进出顺序不同,因此需要使用辅助栈来实现队列。

class ZZHStack {
    private var array: [Int] = [Int]()
    /** initialize your data structure here. */
    init() {
    }

    func push(_ x: Int) {
        array.append(x)
    }

    func pop() -> Int {
        return array.removeLast() 
    }

    func top() -> Int {
        return array.last ?? 0
    }
    func isEmpty() -> Bool {
        return array.isEmpty
    }
}
extension ZZHStack: CustomStringConvertible {
    var description: String {
        if array.count > 0 {
            return "stack: \(array.description)"
        }else {
            return "stack empty"
        }
    }

}
class MyQueue {
    private var mainStack = ZZHStack()
    private var helperStack = ZZHStack()
    private var firstObj = 0
    /** Initialize your data structure here. */
    init() {

    }

    /** Push element x to the back of queue. */
    func push(_ x: Int) {
        if mainStack.isEmpty() == true {
            firstObj = x
        }
        mainStack.push(x)
        //print("after push \(x) mainStack:\(mainStack.description)")

    }

    /** Removes the element from in front of queue and returns that element. */
    func pop() -> Int {
        if helperStack.isEmpty() == true {
            while mainStack.isEmpty() == false {
                helperStack.push(mainStack.pop())
            }
            return helperStack.pop()
        }else {
         return helperStack.pop()
        }
        return 0
    }

    /** Get the front element. */
    func peek() -> Int {
        //print("before peek mainStack:\(mainStack.description)")
        if helperStack.isEmpty() == false {
            return helperStack.top()
        }
        return firstObj
    }

    /** Returns whether the queue is empty. */
    func empty() -> Bool {
        //print("before empty mainStack:\(mainStack.description)")
        return mainStack.isEmpty() && helperStack.isEmpty()
    }
}

496 Next Greater Element I

下一个更大元素 I,难度:简单。
一开始想的方法是先找到第一个数组中元素在第二个数组中的位置,然后再寻找。这样的话,效率差,需要O(m*n)。后来意识到,数组1是数组2的子集,可以先把数组2中各个元素对应的下一个元素找出来,存储在一个Dictionary中。

class Solution {
    func nextGreaterElement(_ nums1: [Int], _ nums2: [Int]) -> [Int] {
        var result = [Int]()
        var array = [Int]()//as stack
        var dic = [Int: Int]()
        for num in nums2 {
            if array.isEmpty == true {
                array.append(num)
            }else {

                while  array.isEmpty == false && num > (array.last)! {
                    dic[(array.popLast())!] = num
                }

                array.append(num)

            }
        }
        while array.isEmpty == false {
            dic[(array.popLast())!] = -1
        }
        for num in nums1 {
            result.append(dic[num]!)
        }
        return result
    }
}

224 Basic Calculator

基本计算器,难度:困难。 基本计算器中只有加减运算和括号,使用两个栈,一个存操作数,一个存操作符。

class ZZHStack<T> {
    private var array = [T]()
    var size: Int {
        get {
            return self.array.count
        }
    }
    var isEmpty: Bool {
        get {
            return self.array.count == 0
        }
    }
    func push(value: T) {
        self.array.append(value)
    }
    func pop() -> T? {
        let last = self.array.removeLast()
        return last
    }
    func peek() -> T? {
        return array.last
    }  
}
class Solution {
    func calculate(_ s: String) -> Int {
        let operatorStack = ZZHStack<Character>()
        let numStack = ZZHStack<Int>()
        var isPreNum = false
        for ch in s {
            switch ch {
            case "(":
                isPreNum = false
                operatorStack.push(value: ch)
            case ")":
                isPreNum = false
                var ope = (operatorStack.pop())!
                while ope != "(" && operatorStack.isEmpty == false {
                    let lastNum = numStack.pop()!
                    let lastSecond = numStack.pop()!
                    if ope == "-" {
                        numStack.push(value: lastSecond - lastNum)
                    }else {
                        numStack.push(value: lastSecond + lastNum)
                    }
                    ope = operatorStack.pop()!
                }
            case "+", "-":
                isPreNum = false
                if operatorStack.isEmpty == true || operatorStack.peek()! == "(" {
                    operatorStack.push(value: ch)
                }else {
                    while operatorStack.isEmpty == false && operatorStack.peek()! != "(" {
                        let ope = operatorStack.pop()!
                        let lastNum = numStack.pop()!
                        let lastSecond = numStack.pop()!
                        let result = ope == "+" ? (lastSecond + lastNum) : (lastSecond - lastNum)
                        numStack.push(value: result)
                    }
                    operatorStack.push(value: ch)
                }
            case " ":
                isPreNum = false
                continue
            default:
                if isPreNum {
                    numStack.push(value: ch.wholeNumberValue! + numStack.pop()! * 10)
                }else {
                    numStack.push(value: ch.wholeNumberValue!)
                }
                isPreNum = true
            }
        }
        while operatorStack.isEmpty == false {
            let ope = (operatorStack.pop())!
            let lastNum = numStack.pop()!
            let lastSecond = numStack.pop()!
            if ope == "-" {
                numStack.push(value: lastSecond - lastNum)
            }else {
                numStack.push(value: lastSecond + lastNum)
            }
        }
        return (numStack.peek())!
    }
}

这样做效率不高,Leecode-cn 上显示执行用时在后10%,也太对不起该题的难度。这种解法存在太多的出入栈,考虑到式子只包含加减和括号,可以有新的思路:把减号当作后一个数字的符号,这样整个运算就变成了加法运算。
以不包含括号的情形为例:

  1. 遇到数字,赋给操作数num,作为操作数--这里需要注意连续数字的情况处理
  2. 遇到+/-,先把当前的num和符号位sign运算后,加到结果result上;保存sign并置num为0

如果存在括号,那么就要去括号,求出下一个数字的符号,比如:

1-(2-3) => 1-2+3

而要做到这一点,需要用到一个栈,遇到左括号时,把当前的符号为入栈,括号内的数字通过栈顶和当前符号位来确定正负。完整的流程:

  1. 遇到左括号,将当前sign压栈
  2. 遇到右括号,弹出栈
  3. 遇到数字,赋给num--同样注意连续数字的情况处理
  4. 遇到+/-,先把当前的num和sign运算后,加到result上;保存sign:当前的sign为下一个操作数准备,需要当前操作符和栈顶操作符共同确定sign值,并置num为0

代码实现:

class Solution {
    func calculate(_ s: String) -> Int {
        let operatorStack = ZZHStack<Int>()
        var result = 0
        var operands = 0
        var sign = 1
        operatorStack.push(value: sign)
        for ch in s {
            switch ch {
            case "(":
                operatorStack.push(value: sign)
            case ")":
                operatorStack.pop()
            case "+", "-":
                result = result + sign * operands
                sign = ((ch == "+") ? 1 : -1) * operatorStack.peek()!
                operands = 0
            case " ":
                continue
            default:
                operands = operands * 10 + ch.wholeNumberValue!

            }
        } 
        return result + sign * operands
    }
}