算法经典面试题(4)——栈和队列

*题目编号为Leetcode中对应的题号。
某位大佬的Leetcode题解参考链接

  1. (20有效的括号) 给定一个只包括 '('')''{''}''['']' 的字符串,判断字符串是否有效。

    有效字符串需满足:

    1. 左括号必须用相同类型的右括号闭合。
    2. 左括号必须以正确的顺序闭合。

    注意空字符串可被认为是有效字符串。

    示例 :

    输入: "()[]{}"
    输出: true
    

    示例 :

    输入: "([)]"
    输出: false
    
     bool isValid(string s) {
          
          
            stack<char> record;
        	for (int i = 0; i < s.size(); i++) {
          
          
        		if (s[i] == '(' || s[i] == '[' || s[i] == '{') {
          
          
        			record.push(s[i]);
        			continue;
        		}
        		else {
          
          // s[i]==)]}
        			if (record.empty())
        				return false;
                    char c=record.top();
                    record.pop();
        			switch (s[i]) {
          
          
        			case ')':
        				if (c != '(')
        					return false;
        				break;
        			case ']':
        				if (c != '[')
        					return false;
        				break;
        			case '}':
        				if (c != '{')
        					return false;
        				break;
        			}
        		}
        	}
        	if (!record.empty())
        		return false;
        	return true;
        }
    
  2. (150逆波兰表达式求值) 根据 逆波兰表示法,求表达式的值。

    有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

    说明:

    • 整数除法只保留整数部分。
    • 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

    示例:

    输入: ["2", "1", "+", "3", "*"]
    输出: 9
    
        /// 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
                        nums.push(atoi(s.c_str()));
                }
                return nums.top();
            }
        };
    
  3. (71简化路径) 以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。

    在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (..) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs 相对路径

    请注意,返回的规范路径必须始终以斜杠 / 开头,并且两个目录名之间必须只有一个斜杠 /。最后一个目录名(如果存在)不能/ 结尾。此外,规范路径必须是表示绝对路径的最短字符串。

    示例:

    输入:"/home/"
    输出:"/home"
    解释:注意,最后一个目录名后面没有斜杠。
    输入:"/../"
    输出:"/"
    解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。
    输入:"/home//foo/"
    输出:"/home/foo"
    解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
    输入:"/a/./b/../../c/"
    输出:"/c"
    
        /// Time Complexity: O(n)
        /// Space Complexity: O(n)
        class Solution {
          
          
        public:
            string simplifyPath(string path) {
          
          
        
                vector<string> stack;
                for(int start = 1, i = 1; i <= path.size(); i ++)
                    if(i == path.size() || path[i] == '/'){
          
          
                        string f = path.substr(start, i - start);
                        if(f.size()){
          
          
                            if(f == ".."){
          
          
                                if(stack.size()) stack.pop_back();
                            }
                            else if(f != ".") stack.push_back(f);
                        }
                        start = i + 1;
                    }
        
                string res = "";
                for(string e: stack)
                    res += "/" + e;
                return res == "" ? "/" : res;
            }
        };
    
  4. (144, 94, 145前中后序遍历二叉树) 二叉树的前序、中序、后续遍历。

        /**
         * Definition for a binary tree node.
         * struct TreeNode {
         *     int val;
         *     TreeNode *left;
         *     TreeNode *right;
         *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
         * };
         */
        // 前序,递归版
        vector<int> preorderTraversal(TreeNode* root) {
          
          
        	vector<int> ret;
        	if (root) {
          
          
        		ret.push_back(root->val);
        		vector<int> ret1 = preorderTraversal(root->left);
        		vector<int> ret2 = preorderTraversal(root->right);
        		ret.insert(ret.end(), ret1.begin(), ret1.end());
        		ret.insert(ret.end(), ret2.begin(), ret2.end());
        	}
        	return ret;
        }
        
        // 栈实现递归版
        class Command{
          
          
            string s;// go print
            TreeNode* node;
            Command(string s_, TreeNode* node_):s(s_), node(node_){
          
          };
        }
        vector<int> preorderTraversal(TreeNode* root) {
          
          
            vector<int> ret;
            if(root==NULL)
                return ret;
            stack<Command> stack;
            stack.push(Command("go", root));
            
            while(!stack.empty()){
          
          
                Command com=stack.top();
                stack.pop();
                
                if(com.s=="print")
                    ret.push_back(com.node->val);
                else{
          
          // go
        			if(com.node->right)
                        stack.push(Command("go", com.node->right));
                    if(com.node->left)
                        stack.push(Command("go", com.node->left));
                    ret.push_back(Command("print", com.node));
                }
            }
            return ret;
        }
    
  5. (341扁平化嵌套列表迭代器) 给你一个嵌套的整型列表。请你设计一个迭代器,使其能够遍历这个整型列表中的所有整数。列表中的每一项或者为一个整数,或者是另一个列表。其中列表的元素也可能是整数或是其他列表。

    示例:

    输入: [[1,1],2,[1,1]]
    输出: [1,1,2,1,1]
    输入: [1,[4,[6]]]
    输出: [1,4,6]
    
        class NestedIterator {
          
          
        
        private:
            vector<int> data;
            int i;
        
        public:
            NestedIterator(vector<NestedInteger> &nestedList) {
          
          
        
                dfs(nestedList);
                i = 0;
            }
        
            int next() {
          
          
        
                return data[i ++];
            }
        
            bool hasNext() {
          
          
                return i < data.size();
            }
        
        private:
            void dfs(const vector<NestedInteger>& nestedList){
          
          
        
                for(const NestedInteger& e: nestedList)
                    if(e.isInteger())
                        data.push_back(e.getInteger());
                    else
                        dfs(e.getList());
            }
        };
    

队列

  • 队列的基本应用:广度优先遍历
    • 树:层序遍历
    • 图:无权图的最短路径
  1. (102二叉树的层序遍历) 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

    示例:
    二叉树:[3,9,20,null,null,15,7],

     	3
       / \
      9  20
        /  \
       15   7
    

    返回其层次遍历结果:

    [
      [3],
      [9,20],
      [15,7]
    ]
    
        vector<vector<int>> levelOrder(TreeNode* root) {
          
          
        	vector<vector<int>> res;
        	if (root == NULL)
        		return res;
        
        	queue<pair<TreeNode*, int>> que;// 节点,所在层数
        	que.push(make_pair(root, 0));
        	while (!que.empty()) {
          
          
        	TreeNode* top = que.front().first;
        		int level = que.front().second;
        		que.pop();
        
        		if (level >= res.size()) {
          
          // 如果是新的一层
        			res.push_back(vector<int>());
        		}
        		res[level].push_back(top->val);
        		
        		if (top->left)
        			que.push(make_pair(top->left, level + 1));
        		if (top->right)
        			que.push(make_pair(top->right, level + 1));
        	}
        	return res;
        }
    
  2. (107二叉树的层序遍历II) 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

    例如:
    给定二叉树 [3,9,20,null,null,15,7],

     	3
       / \
      9  20
    /  \
       15   7
    返回其自底向上的层次遍历为:
    [
      [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;
        }
    };
    
  3. (103二叉树的锯齿形层次遍历) 给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

    例如:
    给定二叉树 [3,9,20,null,null,15,7],

        3
       / \
      9  20
        /  \
       15   7
    

    返回锯齿形层次遍历如下:

    [
      [3],
      [20,9],
      [15,7]
    ]
    
        class Solution {
          
          
        public:
            vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
          
          
        
                vector<vector<int>> res;
                if(!root) return res;
        
                vector<TreeNode*> cur;
                cur.push_back(root);
                int index = 0;
                while(cur.size()){
          
          
        
                    vector<TreeNode*> next;
                    vector<int> tres;
        
                    for(TreeNode* node: cur){
          
          
                        tres.push_back(node->val);
                        if(node->left) next.push_back(node->left);
                        if(node->right) next.push_back(node->right);
                    }
        
                    if(index % 2) reverse(tres.begin(), tres.end());
                    res.push_back(tres);
        
                    cur = next;
                    index ++;
                }
                return res;
            }
        };
    
  4. (199二叉树的右视图) 给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

    示例:

    输入: [1,2,3,null,5,null,4]
    输出: [1, 3, 4]
    解释:
       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;
            }
        };
    
  5. (279完全平方数) 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少

    示例:

    输入: n = 12
    输出: 3 
    解释: 12 = 4 + 4 + 4.
    

    原问题建模

    整个问题转换为一个图论问题。

    从n到0每个数字为一个节点。

    如果两个数字相差一个完全平方数,则连接一条边。

    我们得到一个无权图。

    整个问题转换为:求这个无权图中从n到0的最短路径问题。

        // 基础版
        int numSquares(int n) {
          
          
        		queue<pair<int, int>> que;// 第几个数字,经过几段路径到达该数字
        		que.push(make_pair(n, 0));
        
        		while (!que.empty()) {
          
          
        			int num = que.front().first;
        			int step = que.front().second;
        			que.pop();
        
        			if (num == 0)
        				return step;
        
        			for (int i = 1; num - i * i >= 0; i++) {
          
          
        				que.push(make_pair(num - i * i, step + 1));
        			}
        		}
        }
        
        // 优化版
        int numSquares(int n) {
          
          
        	queue<pair<int, int>> que;// 第几个数字,经过几段路径到达该数字
        	que.push(make_pair(n, 0));
        	vector<bool> visited(n+1, false);// 判断是否遍历过
            visited[n]=true;
            
        	while (!que.empty()) {
          
          
        		int num = que.front().first;
        		int step = que.front().second;
        		que.pop();
                
        		for (int i = 1; ; i++) {
          
          
                    int a=num-i*i;
                    if(a<0)// 提前返回0
                        break;
                    if(a==0)
                        return step+1;
                    if(!visited[a]){
          
          
                        que.push(make_pair(a, step + 1));
                        visited[a]=true;
                    }
        		}
        	}
        }
    
  6. (127、126单词接龙) 给定两个单词(beginWordendWord)和一个字典,找到从 beginWordendWord 的最短转换序列的长度。转换需遵循如下规则:

    1. 每次转换只能改变一个字母。
    2. 转换过程中的中间单词必须是字典中的单词。

    说明:

    • 如果不存在这样的转换序列,返回 0。
    • 所有单词具有相同的长度。
    • 所有单词只由小写字母组成。
    • 字典中不存在重复的单词。
    • 你可以假设 beginWordendWord 是非空的,且二者不相同。

    示例 1:

    输入:
    beginWord = "hit",
    endWord = "cog",
    wordList = ["hot","dot","dog","lot","log","cog"]
    
    输出: 5
    
    解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
     返回它的长度 5。
    
    /// BFS
    /// Time Complexity: O(n*n)
    /// Space Complexity: O(n)
    class Solution {
          
          
    
    public:
        int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
          
          
    
            int end = find(wordList.begin(), wordList.end(), endWord) - wordList.begin();
            if(end == wordList.size())
                return 0;
    
            int begin = find(wordList.begin(), wordList.end(), beginWord) - wordList.begin();
            if(begin == wordList.size())
                wordList.push_back(beginWord);
    
            int n = wordList.size();
    
            vector<vector<bool>> g(n, vector<bool>(n, false));
            for(int i = 0 ; i < wordList.size() ; i ++)
                for(int j = 0 ; j < i ; j ++)
                    g[i][j] = g[j][i] = similar(wordList[i], wordList[j]);
    
            // bfs
            queue<int> q;
            vector<int> step(n, 0);
    
            q.push(begin);
            step[begin] = 1;
            while(!q.empty()){
          
          
                int cur = q.front();
                q.pop();
    
                for(int i = 0 ; i < n ; i ++)
                    if(step[i] == 0 && g[cur][i]){
          
          
                        if(i == end)
                            return step[cur] + 1;
                        step[i] = step[cur] + 1;
                        q.push(i);
                    }
            }
    
            return 0;
        }
    
    private:
        bool similar(const string& word1, const string& word2){
          
          
    
            assert(word1 != "" && word1.size() == word2.size() && word1 != word2);
    
            int diff = 0;
            for(int i = 0 ; i < word1.size() ; i ++)
                if(word1[i] != word2[i]){
          
          
                    diff ++;
                    if(diff > 1)
                        return false;
                }
            return true;
        }
    };
    

优先队列

  • C++: priority_queue默认是最大堆

  • 实现最小堆:

    priority_queue<int, vector<int>, greater<int>> qp;
    
  • 实现自定义比较大小的堆

        bool myCmp(int a, int b){
          
          
            return a%10 < b%10;// 个位数越大越靠前
        }
        priority_queue<int, vector<int>, func<bool(int, int)>> pq(myCmp);
    
  1. (347前k个高频元素) 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

    示例 1:

    输入: nums = [1,1,1,2,2,3], k = 2
    输出: [1,2]
    
        // 基础版
        vector<int> topKFrequent(vector<int>& nums, int k) {
          
          
        		// 记录次数
        		unordered_map<int, int> myMap;
        		for (int i = 0; i < nums.size(); i++)
        			myMap[nums[i]]++;
        
        		// 转为vector
        		vector<pair<int, int>> myVec;// 数字,对应出现的次数
        		for (auto iter = myMap.begin(); iter != myMap.end(); iter++) {
          
          
        			myVec.push_back(make_pair(iter->first, iter->second));
        		}
        
        		// 插入排序,可用快排
        		for (int i = 0; i < myVec.size(); i++)
        			for (int j = i; j > 0; j--) {
          
          
        				if (myVec[j].second > myVec[j - 1].second)
        					swap(myVec[j], myVec[j - 1]);
        			}
        
        		// 取出前k个
        		vector<int> ret;
        		for (int i = 0; i < k; i++)
        			ret.push_back(myVec[i].first);
        
        		return ret;
        }
        
        // 排序版
        vector<int> topKFrequent(vector<int>& nums, int k) {
          
          
            	assert(k > 0);
        		
        		freq.clear();
        		for (int i = 0; i < nums.size(); i++)
        			freq[nums[i]] ++;
        
        		assert(k <= freq.size());
        
        		vector<int> res;
        		for (pair<int, int> p : freq)
        			res.push_back(p.first);
        
        		sort(res.begin(), res.end(), [this](int a, int b) {
          
          
        			if (this->freq[a] != this->freq[b])
        				return this->freq[a] > this->freq[b];
        			return a < b;
        			});
        
        		return vector<int>(res.begin(), res.begin() + k);
        }
        
        // 优先队列版
        vector<int> topKFrequent(vector<int>& nums, int k) {
          
          
            	// 记录次数
        		unordered_map<int, int> myMap;
        		for (int i = 0; i < nums.size(); i++)
        			myMap[nums[i]]++;
        
        		// 维护一个有k个元素的优先队列,当传进一个元素的频率大于该队列中最低频率的元素时,替换之
        		priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;// 频率,元素,从小到大
        		for (auto iter = myMap.begin(); iter != myMap.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
        		vector<int> ret;
        		for (int i = 0; i < k; i++) {
          
          
        			ret.push_back(pq.top().second);
        			pq.pop();
        		}
        		return ret;
        }
    
  2. (合并k个升序链表) 给你一个链表数组,每个链表都已经按升序排列。

    请你将所有链表合并到一个升序链表中,返回合并后的链表。

    示例 1:

    输入:lists = [[1,4,5],[1,3,4],[2,6]]
    输出:[1,1,2,3,4,4,5,6]
    解释:链表数组如下:
    [
      1->4->5,
      1->3->4,
      2->6
    ]
    将它们合并到一个有序链表中得到。
    1->1->2->3->4->4->5->6
    
        /// Using Priority Queue to compare each ListNode
        /// Time Complexity: O(nlogk) where k is len(lists) and n is the nodes number
        /// Space Complexity: O(1)
        class Solution {
          
          
        public:
            ListNode* mergeKLists(vector<ListNode*>& lists) {
          
          
        
                if(lists.size() == 0)
                    return NULL;
        
                ListNode* dummyHead = new ListNode(-1);
                ListNode* curNode = dummyHead;
        
                priority_queue<ListNode*, vector<ListNode*>, CompareListNode> q;
                for(ListNode* node: lists)
                    if(node != NULL)
                        q.push(node);
        
                while(!q.empty()){
          
          
        
                    ListNode* nextNode = q.top();
                    q.pop();
        
        
                    curNode->next = nextNode;
                    if(nextNode->next != NULL)
                        q.push(nextNode->next);
        
                    nextNode->next = NULL;
                    curNode = curNode->next;
                }
        
                ListNode* ret = dummyHead->next;
                delete dummyHead;
                return ret;
            }
        };
    

猜你喜欢

转载自blog.csdn.net/qq_34731182/article/details/113631270