LeetCode系列之栈专题

1. 栈题目概述

https://leetcode-cn.com/tag/stack/

栈是一种操作受限的线性表。

2. 典型题目

2.1 字符串解码

https://leetcode-cn.com/problems/decode-string/

涉及《编译原理》,巴科斯范式(BNF)

2.2 有效的括号

https://leetcode-cn.com/problems/valid-parentheses/

bool isValid(string s) {
        if (s.size() % 2 != 0) return false;

        static const std::unordered_map<char, char> pairs = {
            {')', '('},
            {']', '['},
            {'}', '{'}
        };
        std::stack<char> cstack;
        for (char ch : s) {
            if (pairs.count(ch)) { // right
                if (cstack.empty() || cstack.top() != pairs.at(ch)) {
                    return false;
                }
                cstack.pop();
            } else { // left
                cstack.push(ch);
            }
        }
        return cstack.empty();
    }

有几点值得玩味:

  • 合法的组合都应该是偶数个字符组成的,所以开头的判断可以优化
  • 用unordered_map的count方法判断是否存在,看起来比find方法简洁(不知道效率如何???)
  • 括号map的设计,是有括号到左括号的映射,以简化代码

时间复杂度O(N),空间复杂度O(N+Σ),N是栈中元素个数,Σ是pairs里符号的数量。

2.3 接雨水

https://leetcode-cn.com/problems/trapping-rain-water/

个人认为相比于使用栈的方法,动态规划更容易理解,所以才用了dp。

int trap(vector<int>& height) {
    int n = height.size();
    vector<vector<int>> dp(2, vector<int>(n, 0));
    
    int leftMax = 0;
    for (int i = 0; i < n; i++) {
        leftMax = max(height[i], leftMax);
        dp[0][i] = leftMax;
    }

    int rightMax = 0;
    for (int i = n-1; i >= 0; i--) {
        rightMax = max(height[i], rightMax);
        dp[1][i] = rightMax;
    }

    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += (min(dp[0][i], dp[1][i]) - height[i]);
    }
    return sum;
}

时间复杂度O(N),空间复杂度O(N)。

2.4 柱状图中的最大矩形

https://leetcode-cn.com/problems/largest-rectangle-in-histogram/

int largestRectangleArea(vector<int>& heights) {
    int n = heights.size();
    vector<int> left(n), right(n);
    
    stack<int> monoStack;
    for (int i = 0; i < n; i++) {
        while (!monoStack.empty() && heights[monoStack.top()] >= heights[i]) {
            monoStack.pop();
        }
        left[i] = monoStack.empty() ? -1 : monoStack.top();
        monoStack.push(i);
    }

    // clear the monoStack first
    stack<int>().swap(monoStack);
    for (int i = n - 1; i >= 0; i--) {
        while (!monoStack.empty() && heights[monoStack.top()] >= heights[i]) {
            monoStack.pop();
        }
        right[i] = monoStack.empty() ? n : monoStack.top();
        monoStack.push(i);
    }

    int maxArea = 0;
    for (int i = 0; i < n; i++) {
        maxArea = max(heights[i] * (right[i] - left[i] - 1), maxArea);
    }
    return maxArea;
}

比较喜欢这种思路,类似于《接雨水》的整体思路,但是在计算辅助矩阵的时候,用到了单调栈。比视频题解里单纯用栈的方法,感觉更容易理解。

另外,清空栈的方法可以列举如下两种,stl没有为stack提供clear接口:

// 通过和空stack进行交换
stack<int>().swap(s);
// 通过构造新的空的stack,然后赋值
s = stack<int>();

时间复杂度O(N),空间复杂度O(N)。

2.5 最小栈

https://leetcode-cn.com/problems/min-stack/

采用辅助栈的方法,简单有效。官方题解先入栈了一个INT_MAX,也是一个简化实现的好手。

class MinStack {
public:
    /** initialize your data structure here. */
    MinStack() {
        minStack.push(INT_MAX);
    }
    
    void push(int x) {
        dataStack.push(x);
        minStack.push(min(minStack.top(), x));
    }
    
    void pop() {
        minStack.pop();
        dataStack.pop();
    }
    
    int top() {
        return dataStack.top();
    }
    
    int getMin() {
        return minStack.top();
    }

private:
    std::stack<int> dataStack;
    std::stack<int> minStack;
};

时间复杂度O(1),空间复杂度O(N)。

2.6 最大矩形

https://leetcode-cn.com/problems/maximal-rectangle/

2.7 下一个更大元素

https://leetcode-cn.com/problems/next-greater-element-i/

vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
    unordered_map<int, int> ngeMap;
    vector<int> ret;
    stack<int> monoStack;

    for (int i : nums2) {
        while (!monoStack.empty() && i > monoStack.top()) {
            ngeMap[monoStack.top()] = i;
            monoStack.pop();
        }
        monoStack.push(i);
    }

    while (!monoStack.empty()) {
        ngeMap[monoStack.top()] = -1;
        monoStack.pop();
    }

    for (int i : nums1) {
        ret.push_back(ngeMap[i]);
    }

    return ret;
}

假设nums1长度为M,nums2长度为N;

时间复杂度O(M+N),空间复杂度O(N)。

https://leetcode-cn.com/problems/next-greater-element-ii/

TODO

2.8 二叉树的中序遍历

https://leetcode-cn.com/problems/binary-tree-inorder-traversal/

递归解法:

vector<int> inorderTraversal(TreeNode* root) {
    vector<int> output;
    inorderTraversalRecursive(root, output);
    return output;
}

void inorderTraversalRecursive(TreeNode* root, vector<int>& output) {
    if (root == nullptr) return;
    inorderTraversalRecursive(root->left, output);
    output.push_back(root->val);
    inorderTraversalRecursive(root->right, output);
}

时间复杂度O(N),空间复杂度最差O(N),此时二叉树蜕化成链表形式;平均是O(log N)。

迭代解法:

vector<int> inorderTraversal(TreeNode* root) {
    std::vector<int> ivec;
    std::stack<TreeNode *> s;
    while(root != NULL || !s.empty())
    {
        if (root != NULL) {
            s.push(root);
            root = root->left;
        } else {
            root = s.top();
            ivec.push_back(root->val);
            s.pop();
            root = root->right;
        }
    }
    return ivec;
}

发现几年前写的解法好像比官解要清晰一些。

时间复杂度O(N),空间复杂度O(N)。

2.9 二叉树的前序遍历

https://leetcode-cn.com/problems/binary-tree-preorder-traversal/

递归解法:

vector<int> preorderTraversal(TreeNode* root) {
    vector<int> output;
    preorderTraversalRecursive(root, output);
    return output;
}

void preorderTraversalRecursive(TreeNode* root, vector<int>& output) {
    if (root == nullptr) return;
    output.push_back(root->val);
    preorderTraversalRecursive(root->left, output);
    preorderTraversalRecursive(root->right, output);
}

时间复杂度O(N),空间复杂度最差O(N),此时二叉树蜕化成链表形式;平均是O(log N)。

迭代解法:

vector<int> preorderTraversal(TreeNode* root) {
    vector<int> output;
    if (root == nullptr) return output;

    stack<TreeNode*> nodeStack;
    nodeStack.push(root);

    while (!nodeStack.empty()) {
        TreeNode* node = nodeStack.top();
        output.push_back(node->val);
        nodeStack.pop();

        // 注意这里的顺序
        if (node->right) {
            nodeStack.push(node->right);
        }
        if (node->left) {
            nodeStack.push(node->left);
        }
    }

    return output;
}

时间复杂度O(N),空间复杂度O(N)。

2.10 二叉树的后序遍历

https://leetcode-cn.com/problems/binary-tree-postorder-traversal/

递归解法:

vector<int> postorderTraversal(TreeNode* root) {
    vector<int> output;
    postorderTraversalRecursive(root, output);
    return output;
}

void postorderTraversalRecursive(TreeNode* root, vector<int>& output) {
    if (root == nullptr) return;
    postorderTraversalRecursive(root->left, output);
    postorderTraversalRecursive(root->right, output);
    output.push_back(root->val);
}

时间复杂度O(N),空间复杂度最差O(N),此时二叉树蜕化成链表形式;平均是O(log N)。

迭代解法:

vector<int> postorderTraversal(TreeNode* root) {
    vector<int> output;
    if (root == nullptr) return output;

    stack<TreeNode*> nodeStack;
    nodeStack.push(root);
    while (!nodeStack.empty()) {
        TreeNode* node = nodeStack.top();
        output.push_back(node->val);
        nodeStack.pop();

        // 结合后面的reverse,注意顺序
        if (node->left) {
            nodeStack.push(node->left);
        }
        if (node->right) {
            nodeStack.push(node->right);
        }
    }

    std::reverse(output.begin(), output.end());
    return output;
}

时间复杂度O(N),空间复杂度O(N)。

3. 总结

老师给出的建议:

  • 不要忽视暴力解法,从暴力解法中找出优化的方向。

老师推荐的扩展题目:

  • https://leetcode-cn.com/problems/trapping-rain-water/  接雨水,见2.3
  • https://leetcode-cn.com/problems/daily-temperatures/
  • https://leetcode-cn.com/problems/next-greater-element-i/
  • https://leetcode-cn.com/problems/remove-duplicate-letters/
  • https://leetcode-cn.com/problems/online-stock-span/
  • https://leetcode-cn.com/problems/remove-k-digits/
  • https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray/

monotonic 单调的(单调递增、单调递减、单调栈)

posted @ 2020-08-24 10:58  不写诗的诗人小安  阅读(132)  评论(0编辑  收藏  举报