玩转算法面试(八)栈,队列,优先队列

一,栈

150. 逆波兰表达式求值

根据逆波兰表示法,求表达式的值。

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

说明:

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

示例 1:

输入: ["2", "1", "+", "3", "*"]
输出: 9
解释: ((2 + 1) * 3) = 9

示例 2:

输入: ["4", "13", "5", "/", "+"]
输出: 6
解释: (4 + (13 / 5)) = 6

示例 3:

输入: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
输出: 22
解释:
  ((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

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        int n = tokens.size();
        int ret = 0;
        stack<int> num;
        for(int i = 0; i < n; i++)
        {
            if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/"  )
            {
                int temp1 = num.top();
                num.pop();
                int temp2 = num.top();
                num.pop();
                if(tokens[i] == "+")
                        ret = temp1+temp2;
                else if(tokens[i] == "-")
                        ret = temp2-temp1;
                else if(tokens[i] == "*")
                        ret = temp1*temp2;
                else
                {
                    if(temp1 == 0)
                        ret = 0;
                    else
                        ret = temp2/temp1;
                }
                num.push(ret);
            }
            else
            {
                int temp = atoi(tokens[i].c_str());
                num.push(temp);
            }
        }
        return num.top();
    }
};

71. 简化路径

以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。

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

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

 

示例 1:

输入:"/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。

示例 2:

输入:"/../"
输出:"/"
解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。

示例 3:

输入:"/home//foo/"
输出:"/home/foo"
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。

示例 4:

输入:"/a/./b/../../c/"
输出:"/c"

示例 5:

输入:"/a/../../b/../c//.//"
输出:"/c"

示例 6:

输入:"/a//bc/d//././/.."
输出:"/a/b/c"

class Solution {
public:
    string simplifyPath(string path) {
        if(path.size() == 0)
            return path;
        
        vector<string> res;
        int i = 0;
        while(i < path.size())
        {
            while(i < path.size() && path[i] == '/')
                i++;
            if(i == path.size())
                break;
            
            int start = i;
            while(i < path.size() && path[i] != '/')
                i++;
            string temp = path.substr(start, i - start);
            if(temp == "..")
            {
                if(!res.empty())
                    res.pop_back();
            }
            else if(temp != ".")   
                res.push_back(temp);
        }
        
        if(res.size() == 0)
            return "/";
        string result = "";
        for(int i = 0; i < res.size(); i++)
            result = result + "/" + res[i];
        
        return result;
    }
};

二,队列(BFS广度优先遍历)

107. 二叉树的层次遍历 II

给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

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

    3
   / \
  9  20
    /  \
   15   7

返回其自底向上的层次遍历为:

[
  [15,7],
  [9,20],
  [3]
]

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        queue<TreeNode*> q;
        vector<vector<int>> ret;
        if(root == NULL)
            return ret;
        q.push(root);
        while(!q.empty())
        {
            vector<int>temp;
            int n = q.size();
            for(int i = 0; i < n;i++)
            {
                TreeNode *node = q.front();
                q.pop();
                temp.push_back(node->val);
                if(node->left)
                    q.push(node->left);
                if(node->right)
                    q.push(node->right);
            }
            ret.push_back(temp);
        }
        reverse(ret.begin(), ret.end());
        return ret;
    }
};

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) {
        deque<TreeNode*> q;
        vector<vector<int>> ret;
        if(root == NULL)
            return ret;
        q.push_back(root);
        int flag = 0;
        while(!q.empty())
        {
            vector<int>temp;
            int n = q.size();
            for(int i = 0; i < n;i++)
            {
                if(flag % 2 == 0)
                {
                    TreeNode *node = q.front();
                    q.pop_front();
                    temp.push_back(node->val);
                    if(node->left)
                        q.push_back(node->left);
                    if(node->right)
                        q.push_back(node->right);
                }
                else
                {
                    TreeNode *node = q.back();
                    q.pop_back();
                    temp.push_back(node->val);
                    if(node->right)
                        q.push_front(node->right);
                    if(node->left)
                        q.push_front(node->left);
                }
            }
            flag++;
            ret.push_back(temp);
        }

        return ret;
    }
};

199. 二叉树的右视图

给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

示例:

输入: [1,2,3,null,5,null,4]
输出: [1, 3, 4]
解释:

   1            <---
 /   \
2     3         <---
 \     \
  5     4       <---

思路:巧妙使用二叉树的层序遍历,每次只取每一层的最后一个元素。

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        queue<TreeNode*> q;
        vector<int> ret;
        if(root == NULL)
            return ret;
        q.push(root);
        while(!q.empty())
        {
            int n = q.size();
            for(int i = 0; i < n;i++)
            {
                TreeNode *node = q.front();
                q.pop();
                if(i == n-1)
                    ret.push_back(node->val);
                if(node->left)
                    q.push(node->left);
                if(node->right)
                    q.push(node->right);
            }
        }
        return ret;
    }
};

无权图的最短路径

我们采用了广度优先搜索BFS求出无权图的最短路径。我们这里就会使用队列这个数据结构,首先将初始节点0入队,然后出队;接着将0的所有子节点入队,然后队首出队,同时队首子节点入队,不断循环。

因为我们是不断加入子节点的,同一个节点的子节点到原始节点的距离就一定相同,当然距离不可能超过节点的总个数,距离我们使用ord表示。当然也和之前一样,需要对上一个节点from进行记录。

private:
    Graph &G;
    int s;
    bool *visited;
    int *from;//上一个节点
    int *ord;//s到每一个节点的最短距离

public:
    ShortestPath(Graph &graph, int s):G(graph)
    {
        //算法初始化
        assert( s >= 0 && s < graph.V() );

        visited = new bool[graph.V()];
        from = new int[graph.V()];
        ord = new int[graph.V()];

        for(int i = 0; i < graph.V(); i++){
            visited[i] = false;
            from[i] = -1;
            ord[i] = -1;
        }

        this->s = s;
        queue<int> q;

        //无向图最短路径算法
        q.push( s );
        visited[s] = true;
        ord[s] = 0;
        while( !q.empty() ){
            int v = q.front();
            q.pop();

            typename Graph::adjIterator adj(G, v);
            for(int i = adj.begin(); !adj.end(); i = adj.next()){
                if( !visited[i] ){
                    q.push(i);
                    visited[i] = true;
                    from[i] = v;
                    ord[i] = ord[v] + 1;//距离+1
                }
            }
        }
    }

完成初始化之后,所有节点到v的距离和上一个节点我们就形成了。然后通过from,查找v到w的最短路径。

    void path(int w, vector<int> &vec)
    {
        assert( w >= 0 && w < G.V() );
        stack<int> s;

        //放入栈中
        int p = w;
        while(p != -1){
            s.push(p);
            p = from[p];
        }
        //顺序放入vector
        vec.clear();
        while( !s.empty() ){
            vec.push_back( s.top() );
            s.pop();
        }
    }

    //w为目标节点
    void showPath(int w){
        assert( w >= 0 && w < G.V() );

        vector<int> vec;
        path(w, vec);

        for(int i = 0; i < vec.size(); i++){
            cout<<vec[i];
            if( i == vec.size() - 1 )
                cout<<endl;
            else
                cout<<" -> ";
        }
    }

279. 完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

示例 1:

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

示例 2:

输入: n = 13
输出: 2
解释: 13 = 4 + 9.

第一种方法:动态规划法

class Solution {
public:
    int numSquares(int n) {
        if(n <= 0)
            return 0;
        vector<int>num(n+1, INT_MAX);
        num[0] = 0;
        num[1] = 1;
        for(int i = 2; i <= n; i++)
            for(int j = 1; j*j <= i; j++)
                num[i] = min(num[i], num[i-j*j]+1);
        return num[n];
    }
};

第二种方法:转换成最短路径

class Solution {
public:
    int numSquares(int n) {
        if(n <= 0)
            return 0;
        queue< pair<int, int> > res;
        res.push(make_pair(n, 0));
        vector<bool>visited(n+1, false);
        visited[n] = true;
        
        while(!res.empty())
        {
            int num = res.front().first;
            int step = res.front().second;
            res.pop();
            
            for(int i = 1; ; i++)
            {
                int a = num - i*i ;
                if(a < 0)
                    break;
                if(a == 0)
                    return step+1;
                if(!visited[a])
                {
                    res.push(make_pair(a, step+1));
                    visited[a] = true;
                }  
            }
        }
        
        return n;
    }
};

127. 单词接龙

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:

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

说明:

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

示例 1:

输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

输出: 5

解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
     返回它的长度 5。

示例 2:

输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]

输出: 0

解释: endWord "cog" 不在字典中,所以无法进行转换。

class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        unordered_set<string> dict(wordList.begin(), wordList.end());
        unordered_map<string, int> step;
        queue<string> q;
        step[beginWord] = 1;
        q.push(beginWord);
        
        while(!q.empty())
        {
            string word = q.front();
            q.pop();
            
            for(int i = 0; i < word.size(); i++)
            {
                string newword = word;
                for(char c = 'a'; c <= 'z'; c++)
                {
                    newword[i] = c;    
                    if(dict.count(newword) && newword == endWord)
                        return step[word] + 1;
                    if(dict.count(newword) && step[newword] == 0)
                    {
                        q.push(newword);
                        step[newword] = step[word] + 1;
                    }
                }
            }
        }
        
        return 0;
    }
};

优先队列

使用方法

#include<iostream>
#include<ctime>
#include<queue>

using namespace std;
int main()
{
    srand(time(NULL));
    priority_queue<int> pq;
    //优先队列默认最大堆
    for(int i = 0; i < 10; i++)
    {
        int a = rand()%100;
        pq.push(a);
        cout<<"insert "<<a<<" insert priority_queue"<<endl;
    }

    while(!pq.empty())
    {
        cout<<pq.top()<<" ";
        pq.pop();
    }
    cout<<endl;

    //底层是最小堆
    priority_queue<int, vector<int>, greater<int> > pq2;
    return 0;
}

347. 前 K 个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:

输入: nums = [1], k = 1
输出: [1]

说明:

    你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
    你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> m;
        priority_queue<pair<int, int>> q;
        vector<int> res;
        for (auto a : nums)
            ++m[a];
        //根据频次创建一个最大堆,取出频次最高的前K项
        for (auto it : m) 
            q.push({it.second, it.first});
        for (int i = 0; i < k; ++i) 
        {
            res.push_back(q.top().second); 
            q.pop();
        }
        return res;
    }
};

23. 合并K个排序链表

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

示例:

输入:
[
  1->4->5,
  1->3->4,
  2->6
]
输出: 1->1->2->3->4->4->5->6

第一种方法:思路是每次取出两条,用merge2Lists的方法合并为一条,再将这条和下一条用merge2Lists来合并为一条,以此类推。假设每条链表平均有n个元素,此种时间复杂度是O(2n+3n+…+kn), 为O(nk²),时间复杂度较高。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* L1, ListNode* L2)
    {
        ListNode* L = new ListNode(0);
        ListNode* p = L;
        
        while(L1 && L2)
        {
            if(L1->val < L2->val)
            {
                p->next = L1;
                L1 = L1->next;
                p = p->next;
            }
            else
            {
                p->next = L2;
                L2 = L2->next;
                p = p->next;
            }
        }
        if(L1)
            p->next = L1;
        if(L2)
            p->next = L2;
        
        return L->next;
    }
    
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.size() == 0)
            return NULL;
        if(lists.size() == 1)
            return lists[0];
        
        ListNode* res = NULL;
        res = mergeTwoLists(lists[0], lists[1]);
        for(int i = 2; i < lists.size(); i++)
            res = mergeTwoLists(res, lists[i]);
        
        return res;
        
    }
};

第二种方法:分治法

时间复杂度为O(klogkn)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* L1, ListNode* L2)
    {
        ListNode* L = new ListNode(0);
        ListNode* p = L;
        
        while(L1 && L2)
        {
            if(L1->val < L2->val)
            {
                p->next = L1;
                L1 = L1->next;
                p = p->next;
            }
            else
            {
                p->next = L2;
                L2 = L2->next;
                p = p->next;
            }
        }
        if(L1)
            p->next = L1;
        if(L2)
            p->next = L2;
        
        return L->next;
    }
    
    ListNode* mergeHelper(vector<ListNode*>& lists, int start, int end)
    {
        if(start == end)
            return lists[start];
        
        int mid = start + (end - start)/2;
        ListNode* Left = mergeHelper(lists, start, mid);
        ListNode* Right = mergeHelper(lists, mid+1, end);
        
        return mergeTwoLists(Left, Right);
    }
    
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.size() == 0)
            return NULL;
        
        return mergeHelper(lists, 0, lists.size()-1);
    }
};

第三种方法:使用优先队列

通过优先级队列查找K个有序链表中的最小元素表头,链接到有序链表中去,然后把表头的下一个元素插入到优先级队列。

struct cmp 
    {
        bool operator () (ListNode * a, ListNode * b) {
            return a->val > b->val;
        }
    };
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if(lists.size() == 0)
            return NULL;
        if(lists.size() == 1)
            return lists[0];
        
        priority_queue<ListNode*, vector<ListNode*>, cmp> pq;
        for(int i = 0; i < lists.size(); i++)
        {
            if(lists[i] != NULL)
                pq.push(lists[i]);
        }
         
        ListNode* dummy = new ListNode(0);
        ListNode* head = dummy;
        while(!pq.empty())
        {
            ListNode* temp = pq.top();
            pq.pop();
            head->next = temp;
            head = head->next;
            
            if(temp->next != NULL)
                pq.push(temp->next);
        }
        
        return dummy->next;
    }
};

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值