一. 栈的基础应用 Valid Parentheses
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()"
输出: true
示例 2:
输入: "()[]{}"
输出: true
示例 3:
输入: "(]"
输出: false
示例 4:
输入: "([)]"
输出: false
示例 5:
输入: "{[]}"
输出: true
解题思路
- 如果遇到'({['中任意一个, 都压入栈中。
- 如果遇到')}]'中任意一个, 都从栈中取处元素, 看是否与其相匹配
#include <iostream>
#include <stack>
#include <cassert>
using namespace std;
// 20. Valid Parentheses
// https://leetcode.com/problems/valid-parentheses/description/
// 时间复杂度: O(n)
// 空间复杂度: 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)
return false;
}
if( stack.size() != 0 )
return false;
return true;
}
};
二. 栈和递归的紧密关系 Binary Tree Preorder, Inorder and Postorder Traversal
给定一个二叉树,返回它的 前序 遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,2,3]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
#include <iostream>
#include <vector>
using namespace std;
/// 144. Binary Tree Preorder Traversal
/// https://leetcode.com/problems/binary-tree-preorder-traversal/description/
/// 二叉树的前序遍历
/// 时间复杂度: O(n), n为树的节点个数
/// 空间复杂度: O(h), h为树的高度
/// Definition for a binary tree node.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
__preorderTraversal(root, res);
return res;
}
private:
void __preorderTraversal(TreeNode* node, vector<int> &res){
if(node){
res.push_back(node->val);
__preorderTraversal(node->left, res);
__preorderTraversal(node->right, res);
}
}
};
计算机实现递归的本质,是运用了栈。 每次遇到递归, 就会把相应程序放入栈中。
三. 运用栈模拟递归
上一节的问题非递归解法
- 本质就是模拟 计算机的 利用栈实现递归的原理
#include <iostream>
#include <vector>
#include <stack>
#include <cassert>
using namespace std;
/// 144. Binary Tree Preorder Traversal
/// https://leetcode.com/problems/binary-tree-preorder-traversal/description/
/// 非递归的二叉树的前序遍历
/// 时间复杂度: O(n), n为树的节点个数
/// 空间复杂度: O(h), h为树的高度
/// Definition for a binary tree node.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
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;
}
};
-
利用这样的思路, 可以将所有递归算法改编为非递归算法
-
中序遍历和后序遍历 稍微修改代码就可以实现
四. 队列的典型应用 Binary Tree Level Order Traversal
队列的主要处理问题- 广度优先遍历
- 在树中: 解决层序遍历
- 在图中: 解决无权图的最短路径
例题102
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
例如:
给定二叉树: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
解法
#include <iostream>
#include <vector>
#include <queue>
#include <cassert>
using namespace std;
/// 102. Binary Tree Level Order Traversal
/// https://leetcode.com/problems/binary-tree-level-order-traversal/description/
/// 二叉树的层序遍历
/// 时间复杂度: O(n), n为树的节点个数
/// 空间复杂度: O(n)
/// Definition for a binary tree node.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if(root == NULL)
return res;
queue<pair<TreeNode*,int>> q;
q.push(make_pair(root, 0)); //level为0
while(!q.empty()){
TreeNode* node = q.front().first;
int level = q.front().second;
q.pop();
if(level == res.size()) // level从0开始的 所以 res还没有存放最新的层
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;
}
};
五. BFS和图的最短路径 Perfect Square
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9.
解题思路
对问题建模:
整个问题转化为一个图论问题
从n到0, 每个数字表示一个节点
如果两个数字x到y相差一个完全平方数, 则连接一条边
我们得到了一个无权图
原问题转化成, 求这个无权图中从n到0的最短路径
代码
- 简单实现
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
// 279. Perfect Squares
// https://leetcode.com/problems/perfect-squares/description/
// 该方法会导致 Time Limit Exceeded 或者 Memory Limit Exceeded
//
// 时间复杂度: O(2^n)
// 空间复杂度: O(2^n)
class Solution {
public:
int numSquares(int n) {
queue<pair<int, int>> q; // 第一个int 表示到达哪个数字 第二个int 经历了几段路径
q.push(make_pair(n, 0));
while(!q.empty()){
int num = q.front().first;
int step = q.front().second;
q.pop();
if(num == 0) // num==0就表示结束了
return step;
for(int i = 1 ; num - i*i >= 0 ; i ++)
q.push(make_pair(num - i * i, step + 1));
}
throw invalid_argument("No Solution.");
}
};
上面代码是有问题的, 问题主要是性能方面
- 在队列中推入了太多和最后结果无关的数字
- 如果n很大, 就会非常冗余
代码优化
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
// 279. Perfect Squares
// https://leetcode.com/problems/perfect-squares/description/
// 使用visited数组,记录每一个入队元素
//
// 时间复杂度: O(n)
// 空间复杂度: O(n)
class Solution {
public:
int numSquares(int n) {
queue<pair<int, int>> q;
q.push(make_pair(n, 0));
vector<bool> visited(n+1, false);
visited[n] = true; // 被访问过的都标为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; // n==0的判断 提前, 减少运算量
q.push(make_pair(a, step + 1));
visited[a] = true;
}
}
}
throw invalid_argument("No Solution.");
}
};
六. 优先队列
- c++中优先队列的使用方法
#include <iostream>
#include <queue>
#include <ctime>
using namespace std;
bool myCmp(int a , int b){
if(a%10 != b%10)
return a%10 > b%10;
return a > b;
}
int main() {
srand(time(NULL));
// 默认的priority queue, 底层是最大堆
priority_queue<int> pq;
for(int i = 0 ; i < 10 ; i ++){
int num = rand() % 100;
pq.push(num);
cout << "insert " << num << " in priority queue." << endl;
}
while(!pq.empty()){
cout << pq.top() << " ";
pq.pop();
}
cout << endl << endl;
// 使用greater的priority queue, 底层是最小堆
priority_queue<int, vector<int>, greater<int>> pq2; // vector<int> 底层数据结构 greater<int> c++定义好的参数, 指定队列底层为最小堆
for(int i = 0; i < 10; i ++){
int num = rand() % 100;
pq2.push(num);
cout << "insert " << num << " in priority queue." << endl;
}
while(!pq2.empty()){
cout << pq2.top() << " ";
pq2.pop();
}
cout << endl << endl;
// 使用自定义Comparator的priority queue
priority_queue<int, vector<int>, function<bool(int,int)>> pq3(myCmp);
for(int i = 0; i < 10; i ++){
int num = rand() % 100;
pq3.push(num);
cout << "insert " << num << " in priority queue." << endl;
}
while(!pq3.empty()){
cout << pq3.top() << " ";
pq3.pop();
}
return 0;
}
七. 优先队列相关的算法问题 Top K Frequent Elements
给定一个非空的整数数组,返回其中出现频率前 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 是数组的大小。
解题思路
- 维护优先队列, 时间复杂度是O(nlogK)
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <cassert>
using namespace std;
// 347. Top K Frequent Elements
// https://leetcode.com/problems/top-k-frequent-elements/description/
// 时间复杂度: O(nlogk)
// 空间复杂度: O(n + k)
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个元素
// 在优先队列中,按照频率排序,所以数据对是 (频率,元素) 的形式 对应pair的结构, 和map中的key value调换
// 创建底层是最小堆 的优先队列
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;
}
};
int main() {
int nums[] = {1, 1, 1, 2, 2, 3};
vector<int> vec(nums, nums + sizeof(nums)/sizeof(int));
int k = 2;
vector<int> res = Solution().topKFrequent(vec, 2);
for( int i = 0 ; i < res.size() ; i ++ )
cout<<res[i]<<" ";
cout<<endl;
return 0;
}