【leetcode真题】栈,队列,优先队列

添加链接描述## 栈和队列虽然是简单的数据结构,但是使用这些简单的数据结构所解决的算法问题不一定简单。在这一章里,我们将来探索,和栈与队列相关的算法问题。

栈的基础应用:

问题1:Valid Parentheses

Given a string containing just the characters ‘(’, ‘)’, ‘{’, ‘}’, ‘[’ and ‘]’, determine if the input string is valid.
An input string is valid if:
Open brackets must be closed by the same type of brackets.
Open brackets must be closed in the correct order.
Note that an empty string is also considered valid.
Example 1:
Input: “()”
Output: true
Example 2:
Input: “()[]{}”
Output: true
Example 3:
Input: “(]”
Output: false
Example 4:
Input: “([)]”
Output: false
Example 5:
Input: “{[]}”
Output: true

// Using Stack
// Time Complexit**y: O(n)
// Space Complexity: O(n)
class Solution {
public:
    bool isValid(string s) {

        stack<char> stack;
        for( int i = 0 ; i < s.size() ; i ++ )
            if( s[i] == '(' || s[i] == '{' || s[i] == '[')
                stack.push(s[i]);
            else{

                if( stack.size() == 0 )
                    return false;

                char c = stack.top();  //拿一个栈顶元素
                stack.pop();   //推出

                char match;     //相对应匹配的
                if( s[i] == ')' )
                    match = '(';
                else if( s[i] == ']' )
                    match = '[';
                else{
                    assert( s[i] == '}' );
                    match = '{';
                }

                if(c != match)   //判断当前栈顶元素是否匹配对应的match
                    return false;
            }
        //检查当前的栈是否为空
        if( stack.size() != 0 )
            return false;

        return true;
    }
};

问题2:Evaluate Reverse Polish Notation

相关知识点:逆波兰表达式的求法
Evaluate the value of an arithmetic expression in Reverse Polish Notation.
Valid operators are +, -, , /. Each operand may be an integer or another expression.
Note:
Division between two integers should truncate toward zero.
The given RPN expression is always valid. That means the expression would always evaluate to a result and there won’t be any divide by zero operation.
Example 1:
Input: [“2”, “1”, “+”, “3”, "
"]
Output: 9
Explanation: ((2 + 1) * 3) = 9
Example 2:
Input: [“4”, “13”, “5”, “/”, “+”]
Output: 6
Explanation: (4 + (13 / 5)) = 6
Example 3:
Input: [“10”, “6”, “9”, “3”, “+”, “-11”, “", “/”, "”, “17”, “+”, “5”, “+”]
Output: 22
Explanation:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

/// Using Stacks
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class Solution {
public:
    int evalRPN(vector<string>& tokens) {

        stack<int> nums;
        //一直循环遍历
        for(const string& s: tokens){
            //遇到运算符就要开始处理
            if(s == "+" || s == "-" || s == "*" || s == "/"){
                int a = nums.top();
                nums.pop();
                int b = nums.top();
                nums.pop();

                if(s == "+")
                    nums.push(b + a);
                else if(s == "-")
                    nums.push(b - a);
                else if(s == "*")
                    nums.push(b * a);
                else if(s == "/")
                    nums.push(b / a);
            }
            else
                //将非运算符转化成int型数组
                nums.push(atoi(s.c_str()));
        }
        return nums.top();
    }
};

运用栈模拟递归:

问题2:Binary Tree Preorder Traversal

原题

// 模拟系统栈
// My Non-Recursive
// Time Complexity: O(n), n is the node number in the tree
// Space Complexity: O(h), h is the height of the tree
class Solution {

private:
    struct Command{
        string s;   // go, print 描述命令
        TreeNode* node;
        Command(string s, TreeNode* node): s(s), node(node){}
    };

public:
    vector<int> preorderTraversal(TreeNode* root) {

        vector<int> res;
        if(root == NULL)
            return res;

        stack<Command> stack;
        //把根节点推入
        stack.push(Command("go", root));
        while(!stack.empty()){
            Command command = stack.top();//取栈顶元素
            stack.pop();//出栈
            if(command.s == "print")
                res.push_back(command.node->val);
            else{
                assert(command.s == "go");
                if(command.node->right)
                    stack.push(Command("go",command.node->right));
                if(command.node->left)
                    stack.push(Command("go",command.node->left));
                stack.push(Command("print", command.node));
            }
        }
        return res;
    }
};

问题2:Binary Tree Inorder Traversal

// My Non-Recursive
// Time Complexity: O(n), n is the node number in the tree
// Space Complexity: O(h), h is the height of the tree
class Solution {

private:
    struct Command{
        string s;   // go, print
        TreeNode* node;
        Command(string s, TreeNode* node): s(s), node(node){}
    };

public:
    vector<int> inorderTraversal(TreeNode* root) {

        vector<int> res;
        if( root == NULL )
            return res;

        stack<Command> stack;
        stack.push(Command("go", root));
        while( !stack.empty() ){
            Command command = stack.top();
            stack.pop();

            if(command.s == "print")
                res.push_back(command.node->val);
            else{
                assert(command.s == "go");
                if(command.node->right)
                    stack.push(Command("go",command.node->right));
                stack.push(Command("print", command.node));
                if(command.node->left)
                    stack.push(Command("go",command.node->left));
            }
        }
        return res;
    }
};

问题3:Binary Tree Inorder Traversal

// My Non-Recursive
// Time Complexity: O(n), n is the node number in the tree
// Space Complexity: O(h), h is the height of the tree
class Solution {

private:
    struct Command{
        string s;   // go, print
        TreeNode* node;
        Command(string s, TreeNode* node): s(s), node(node){}
    };

public:
    vector<int> postorderTraversal(TreeNode* root) {

        vector<int> res;
        if(root == NULL)
            return res;

        stack<Command> stack;
        stack.push(Command("go", root) );
        while(!stack.empty()){
            Command command = stack.top();
            stack.pop();

            if(command.s == "print")
                res.push_back(command.node->val);
            else{
                assert(command.s == "go");
                stack.push(Command("print", command.node));
                if(command.node->right)
                    stack.push(Command("go",command.node->right));
                if(command.node->left)
                    stack.push(Command("go",command.node->left));
            }
        }
        return res;
    }
};

队列:基本应用-广度优先遍历

树:层序遍历
图:无权图的广度优先遍历

问题4:Binary Tree Level Order Traversal

Medium220159Add to ListShareGiven a binary tree, return the level order traversal of its nodes’ values. (ie, from left to right, level by level).
For example:
Given binary tree [3,9,20,null,null,15,7],
3
/
9 20
/
15 7
return its level order traversal as:
[
[3],
[9,20],
[15,7]
]

原题


/// BFS
/// Time Complexity: O(n), where n is the number of nodes in the tree
/// Space Complexity: O(n)
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {

        vector<vector<int>> res;
        if(root == NULL)
            return res;

        queue<pair<TreeNode*,int>> q; //pair存结点和存层数
        q.push(make_pair(root, 0));

        while(!q.empty()){

            TreeNode* node = q.front().first;
            int level = q.front().second;
            q.pop();

            if(level == res.size())
                res.push_back(vector<int>());
            assert( level < res.size() );

            res[level].push_back(node->val);
            if(node->left)
                q.push(make_pair(node->left, level + 1 ));
            if(node->right)
                q.push(make_pair(node->right, level + 1 ));
        }

        return res;
    }
};

问题5: Binary Tree Level Order Traversal II(倒序)

Given a binary tree, return the bottom-up level order traversal of its nodes’ values. (ie, from left to right, level by level from leaf to root).
For example:
Given binary tree [3,9,20,null,null,15,7],
3
/
9 20
/
15 7
return its bottom-up level order traversal as:
[
[15,7],
[9,20],
[3]
]

/// BFS
/// Time Complexity: O(n), where n is the number of nodes in the tree
/// Space Complexity: O(n)
class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {

        vector<vector<int>> res;
        if(root == NULL)
            return res;

        queue<pair<TreeNode*,int>> q;
        q.push(make_pair(root, 0));

        while(!q.empty()){

            TreeNode* node = q.front().first;
            int level = q.front().second;
            q.pop();

            if(level == res.size())
                res.push_back(vector<int>());
            assert( level < res.size() );

            res[level].push_back(node->val);
            if(node->left)
                q.push(make_pair(node->left, level + 1 ));
            if(node->right)
                q.push(make_pair(node->right, level + 1 ));
        }

        reverse(res.begin(), res.end());
        return res;
    }
};

问题6: Binary Tree Zigzag Level Order Traversal(之字形输出)

原题

class Solution {
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {

        vector<vector<int>> res;
        if(!root) return res;

        queue<pair<TreeNode*, int>> q;
        q.push(make_pair(root, 0));
        while(!q.empty()){

            TreeNode* node = q.front().first;
            int level = q.front().second;
            if(level >= res.size()) res.push_back({});
            res[level].push_back(node->val);
            q.pop();

            if(node->left) q.push(make_pair(node->left, level + 1));
            if(node->right) q.push(make_pair(node->right, level + 1));
        }
        //根据二叉树的性质遍历
        for(int i = 1; i < res.size(); i += 2)
            reverse(res[i].begin(), res[i].end());
        return res;
    }
};

问题7:Binary Tree Right Side View

原题
Given a binary tree, imagine yourself standing on the right side of it, return the values of the nodes you can see ordered from top to bottom.
Example:
Input: [1,2,3,null,5,null,4]
Output: [1, 3, 4]
Explanation:
1 <—
/
2 3 <—
\
5 4 <—

解法一:


class Solution {
public:
  vector<int> rightSideView(TreeNode* root) {

      vector<int> res;  //保存结果
      if(!root) return res;

      vector<TreeNode*> cur = {root};
      while(cur.size()){
          res.push_back(cur.back()->val);  //层次遍历完每一层就把最后一个元素输出

          vector<TreeNode*> next;  //保存下一个
          //遍历第一层
          for(TreeNode* node: cur){
              if(node->left) next.push_back(node->left);
              if(node->right) next.push_back(node->right);
          }
          cur = next;
      }
      return res;
  }
};

解法二:

class Solution {

private:
    vector<int> res;

public:
    vector<int> rightSideView(TreeNode* root) {

        if(!root) return res;

        dfs(root, 0);
        return res;
    }

private:
    void dfs(TreeNode* node, int d){

        if(res.size() <= d) res.push_back(0);
        res[d] = node->val;

        if(node->left) dfs(node->left, d + 1);
        if(node->right) dfs(node->right, d + 1);
    }
};

问题8:Perfect Squares

Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, …) which sum to n.
Example 1:
Input: n = 12
Output: 3
Explanation: 12 = 4 + 4 + 4.
Example 2:
Input: n = 13
Output: 2
Explanation: 13 = 4 + 9.

此题可以化成无向图

/// BFS 广度优先遍历
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class Solution {
public:
    int numSquares(int n) {

        if(n == 0)
            return 0;

        queue<pair<int, int>> q;  //第一个int具体是第几个数字,第二个int是经历了几段路径走到了该数
        q.push(make_pair(n, 0));

        vector<bool> visited(n + 1, false);  //记录边是否已被访问过
        visited[n] = true;

        while(!q.empty()){
            int num = q.front().first;   //取出队首的元素
            int step = q.front().second;   //表示走了几步
            q.pop();//出队


            for(int i = 1; num - i * i >= 0; i ++){
                int a = num - i * i;
                if(!visited[a]){
                    if(a == 0) return step + 1;
                    q.push(make_pair(a, step + 1));
                    visited[a] = true;
                }
            }
        }

        throw invalid_argument("No Solution.");
    }
};

问题9:Word Ladder 词语阶梯(此题与走迷宫类似)BFS

原题
Given two words (beginWord and endWord), and a dictionary’s word list, find the length of shortest transformation sequence from beginWord to endWord, such that:
Only one letter can be changed at a time.
Each transformed word must exist in the word list. Note that beginWord is not a transformed word.

//count这个函数使用一对迭代器和一个值做参数,返回这个值出现次数的统计结果。
class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        unordered_set<string> wordSet(wordList.begin(), wordList.end());  //保存所有字典
        if (!wordSet.count(endWord)) return 0;
        unordered_map<string, int> pathCnt{{{beginWord, 1}}};//来建立某条路径结尾单词和该路径长度之间的映射,并把起始单词映射为1
        queue<string> q{{beginWord}}; //把起始单词排入队列中
        while (!q.empty()) {
            string word = q.front(); q.pop();  //取出起始单词
            for (int i = 0; i < word.size(); ++i) {
                string newWord = word;
                //进行对单词的更换操作
                for (char ch = 'a'; ch <= 'z'; ++ch) {
                    newWord[i] = ch;
                    //如果此时和结尾单词相同了,就可以返回取出词在哈希表中的值加一
                    if (wordSet.count(newWord) && newWord == endWord) return pathCnt[word] + 1;
                    //如果替换词在字典中存在但在哈希表中不存在,则将替换词排入队列中,并在哈希表中的值映射为之前取出词加一	
                    if (wordSet.count(newWord) && !pathCnt.count(newWord)) {
                        q.push(newWord);
                        pathCnt[newWord] = pathCnt[word] + 1;
                    }   
                }
            }
        }
        return 0;
    }
};

附上原创作者的链接:词语阶梯

优先队列:底层实现是最大堆(默认情况下) 用greater是最小堆

问题10:Top K Frequent Elements

Given a non-empty array of integers, return the k most frequent elements.
Example 1:
Input: nums = [1,1,1,2,2,3], k = 2
Output: [1,2]
Example 2:
Input: nums = [1], k = 1
Output: [1]


/// Priority Queue
/// Time Complexity: O(nlogn)
/// Space Complexity: O(n)
class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {

        assert(k > 0);

        // 统计每个元素出现的频率
        unordered_map<int,int> freq;
        for(int i = 0 ; i < nums.size() ; i ++ )
            freq[nums[i]] ++;

        assert(k <= freq.size());

        // 扫描freq,维护当前出现频率最高的k个元素
        // 在优先队列中,按照频率排序,所以数据对是 (频率,元素) 的形式
        //底层是最小堆
        priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pq;
        for(unordered_map<int,int>::iterator iter = freq.begin();
            iter != freq.end(); iter ++ ){
            if(pq.size() == k){
                if(iter->second > pq.top().first){
                    pq.pop();
                    pq.push( make_pair(iter->second, iter->first));  //频率,元素
                }
            }
            else
                pq.push(make_pair(iter->second , iter->first));
        }

        //保存结果集
        vector<int> res;
        while(!pq.empty()){
            res.push_back(pq.top().second);
            pq.pop();
        }

        return res;
    }
};

问题10:Merge k Sorted Lists[hard]

原题
Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
Example:
Input:
[
1->4->5,
1->3->4,
2->6
]
Output: 1->1->2->3->4->4->5->6

这道题让我们合并k个有序链表,最终合并出来的结果也必须是有序的,之前做过一道 Merge Two Sorted Lists,是混合插入两个有序链表。这道题增加了难度,变成合并k个有序链表了,但是不管合并几个,基本还是要两两合并。那么首先考虑的方法是能不能利用之前那道题的解法来解答此题。答案是肯定的,但是需要修改,怎么修改呢,最先想到的就是两两合并,就是前两个先合并,合并好了再跟第三个,然后第四个直到第k个。这样的思路是对的,但是效率不高,没法通过 OJ,所以只能换一种思路,这里就需要用到分治法 Divide and Conquer Approach。简单来说就是不停的对半划分,比如k个链表先划分为合并两个 k/2 个链表的任务,再不停的往下划分,直到划分成只有一个或两个链表的任务,开始合并。举个例子来说比如合并6个链表,那么按照分治法,首先分别合并0和3,1和4,2和5。这样下一次只需合并3个链表,再合并1和3,最后和2合并就可以了。代码中的k是通过 (n+1)/2 计算的,这里为啥要加1呢,这是为了当n为奇数的时候,k能始终从后半段开始,比如当 n=5 时,那么此时 k=3,则0和3合并,1和4合并,最中间的2空出来。当n是偶数的时候,加1也不会有影响,比如当 n=4 时,此时 k=2,那么0和2合并,1和3合并,完美解决问题,参见代码如下:

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if (lists.empty()) return NULL;
        int n = lists.size();
        while (n > 1) {
        /*
        代码中的k是通过 (n+1)/2 计算的,这里为啥要加1呢,这是为了当n为奇数的时候,k能始终从后半段开始,比如当 n=5 时,那么此时 k=3,则0和3合并,1和4合并,最中间的2空出来。当n是偶数的时候,加1也不会有影响,比如当 n=4 时,此时 k=2,那么0和2合并,1和3合并,完美解决问题
        */
            int k = (n + 1) / 2;
            for (int i = 0; i < n / 2; ++i) {
                lists[i] = mergeTwoLists(lists[i], lists[i + k]);
            }
            n = k;
        }
        return lists[0];
    }


	//合并两个链表
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode *dummy = new ListNode(-1), *cur = dummy;
        while (l1 && l2) {
        //哪个元素小cur->next指向谁,再继续在改链表中寻找
            if (l1->val < l2->val) {
                cur->next = l1;
                l1 = l1->next;
            } else {
                cur->next = l2;
                l2 = l2->next;
            }
            cur = cur->next;
        }
        if (l1) cur->next = l1;   //l2已经存完了
        if (l2) cur->next = l2;   //l1已经存完了
        return dummy->next; 
    }
};

我们再来看另一种解法,这种解法利用了最小堆这种数据结构,首先把k个链表的首元素都加入最小堆中,它们会自动排好序。然后每次取出最小的那个元素加入最终结果的链表中,然后把取出元素的下一个元素再加入堆中,下次仍从堆中取出最小的元素做相同的操作,以此类推,直到堆中没有元素了,此时k个链表也合并为了一个链表,返回首节点即可,参见代码如下:

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        auto cmp = [](ListNode*& a, ListNode*& b) {
            return a->val > b->val;
        };
        priority_queue<ListNode*, vector<ListNode*>, decltype(cmp) > q(cmp);  //由小到大排序
        for (auto node : lists) {
            if (node) q.push(node);
        }
        ListNode *dummy = new ListNode(-1), *cur = dummy;
        //从优先队列中取出元素
        while (!q.empty()) {
            auto t = q.top(); q.pop();
            cur->next = t;
            cur = cur->next;
            if (cur->next) q.push(cur->next);
        }
        return dummy->next;
    }
};
趁这个机会来总结一下最近在学习吧,最近感觉都挺飘的,总想一步登天,不脚踏实地学习,要调整一下心态,重新出发,莫急,加油!!!!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值