添加链接描述## 栈和队列虽然是简单的数据结构,但是使用这些简单的数据结构所解决的算法问题不一定简单。在这一章里,我们将来探索,和栈与队列相关的算法问题。
栈的基础应用:
问题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;
}
};