##栈和队列
//
// Created by yzm on 12/5/18.
//
#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <stack>
#include <assert.h>
#include <stdexcept>
#include <limits.h>
#include <unordered_set>
#include <unordered_map>
#include <functional>//C++11新特性
using namespace std;
//Definition for a binary tree node.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
/**
* 20.有效的括号
* 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
* 有效字符串需满足:
* 左括号必须用相同类型的右括号闭合。
* 左括号必须以正确的顺序闭合。
* */
class Solution1 {
public:
bool isValid(string s) {
if(s.size() == 0||s.size())
return false;
stack<char> mystack;
for(int i = 0; i < s.size(); i++){
if (s[i] == '(' || s[i] == '[' || s[i] == '{') {
mystack.push(s[i]);
} else{
if(mystack.size() == 0)
return false;
char match;
switch (s[i]){
case ')':
match = '(';
break;
case ']':
match = '[';
break;
case '}':
match = '{';
break;
default:
break;
}
char c = mystack.top();
mystack.pop();
if (c != match)
return false;
}
}
if (mystack.size() != 0)
return false;
return true;
}
};
/**
* 150.逆波兰表达式求值
*
* */
// 思路: 遇到数字字符就放入栈,遇到运算符就推出栈中的两个,一个作为运算符的左操作数另一个作为右操作数(近)
class Solution2 {
public:
int evalRPN(vector<string>& tokens) {
stack<int> stack;
for (int i = 0; i < tokens.size(); i++) {
// 遇到运算符
if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
int op2 = stack.top(); stack.pop();
int op1 = stack.top(); stack.pop();
stack.push(op(op1, op2, tokens[i][0]));
}
else
// 遇到数字
stack.push(stoi(tokens[i]));
}
return stack.top();
}
int op(int op1, int op2, char optor) {
if (optor == '+')
return op1 + op2;
else if (optor == '-')
return op1 - op2;
else if (optor == '*')
return op1*op2;
else
return op1 / op2;
}
};
//栈和和递归的紧密关系
/**
* 144.二叉树前序遍历非递归实现
* */
struct Command{
string s;
TreeNode* node;
Command(string s,TreeNode* node):s(s),node(node){}
};
class Solution3 {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int>res;
if(root == nullptr)
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;
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int>res;
if(root == nullptr)
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;
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int>res;
if(root == nullptr)
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;
}
};
/**
* 102.二叉树的层序遍历
* */
// 思路: 还是用辅助的队列完成整个数的层序遍历,只不过分析后发现还要存储节点的层序值,所以将queue中的数据结构考虑为一个pair对
class Solution4 {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
if (root == NULL)
return vector<vector<int>>();
vector<vector<int>> res;
queue< pair<TreeNode*, int> > q;
// 将根节点的指针和层序放入队列
q.push(make_pair(root, 0));
while (!q.empty()) {
// 取出队列中的头数据,并pop
TreeNode* node = q.front().first;
int level = q.front().second;
q.pop();
// 将当前的节点信息放入res结果中
// 首先判断要不要为当前节点开辟一个新的vector<int>
if (level == res.size()) {
res.push_back(vector<int>());
}
res[level].push_back(node->val);
// 将node的左右子节点放入队列
if (node->left)
q.push(make_pair(node->left, level + 1));
if (node->right)
q.push(make_pair(node->right, level + 1));
}
return res;
}
};
/**
* 102.二叉树的层序遍历2
* */
// 思路: 还是用辅助的队列完成整个数的层序遍历,只不过分析后发现还要存储节点的层序值,所以将queue中的数据结构考虑为一个pair对
// 思路: 套用上一题的思路,只不过最终输出的形式需要倒置一下
class Solution5 {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
if (root == NULL)
return vector<vector<int>>();
vector<vector<int>> res_tmp;
queue< pair<TreeNode*, int> > q;
q.push(make_pair(root, 0));
// 层序遍历
while (!q.empty()) {
// 取出队列头数据,并pop出
TreeNode* node = q.front().first;
int level = q.front().second;
q.pop();
// 将当前的节点信息放入res结果中
// 首先判断要不要为当前节点开辟一个新的vector<int>
if (res_tmp.size() == level)
res_tmp.push_back(vector<int>());
res_tmp[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));
}
// 倒序输出
vector<vector<int>> res;
for (int i = 0; i < res_tmp.size(); i++) {
res.push_back(vector<int>());
for (int j = 0; j < res_tmp[res_tmp.size() - 1 - i].size(); j++) { // 从最后一层开始
res[i].push_back(res_tmp[res_tmp.size() - 1 - i][j]);
}
}
return res;
}
};
/**
* 103.二叉树的锯齿形层次遍历
* */
// 思路: 其实层序遍历的过程大体上是一样的,只不过在最终输出的形式上略有差别,以符合回形针的读取形式
class Solution6 {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
if (root == NULL)
return vector<vector<int>>();
queue< pair<TreeNode*, int> > q;
q.push(make_pair(root, 0));
vector<vector<int>> res;
while (!q.empty()) {
TreeNode* node = q.front().first;
int level = q.front().second;
q.pop();
if (level == res.size())
res.push_back(vector<int>());
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));
}
// 整理成回形针的形式
vector<vector<int>> resAli = res;
for (int i = 1; i < res.size(); i += 2) {
for (int j = 0; j < res[i].size(); j++) {
resAli[i][res[i].size() - 1 - j] = res[i][j];
}
}
return resAli;
}
};
/**
* 199.二叉树的右视图
*
* */
// 思路: 其实这题更简单,还是本质上还是层序遍历,只不过每次放置子节点的时候可以先放置右节点,完了将每一层的第一个数据取出即可
class Solution7 {
public:
vector<int> rightSideView(TreeNode* root) {
if (root == NULL)
return vector<int>();
queue< pair<TreeNode*, int> > q;
q.push(make_pair(root, 0));
vector<vector<int>> res;
while (!q.empty()) {
TreeNode* node = q.front().first;
int level = q.front().second;
q.pop();
if (level == res.size())
res.push_back(vector<int>());
res[level].push_back(node->val);
// 接着遍历,先放右节点,那么每一层的数据都是先从右边开始了
if (node->right)
q.push(make_pair(node->right, level + 1));
if (node->left)
q.push(make_pair(node->left, level + 1));
}
vector<int> result;
for (int i = 0; i < res.size(); i++)
result.push_back(res[i][0]);
return result;
}
};
/**
* 279.完全平方数
* 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少
* */
// 思路: 这题需要将原问题抽象成一个图的最短路径问题,怎么分析抽象呢?
// 首先,题目说是要求组成和的完全平方数的个数最少,最少对应最短,也就和最短路径上的节点数对应起来了,
// 那么到这,问题就转换到了如何去构建这个图,表达这个图呢?
// 这个就要和完全平方数结合起来,若当前点与周围点相差一个完全平方数,那么两点之间就可以连接一条线,这样就构成了一个有向无权图了
class Solution {
public:
int numSquares(int n) {
// 既然是求图的最短路径,那么自然要用到队列的辅助数据结构
queue< pair<int, int> > q;
q.push(make_pair(n, 0));
// 辅助的访问记录数据,这里为n+1个数据是因为0,,...,n有n+1个数据
vector<bool> visited(n + 1, false);
// 将当前节点设置为访问过
visited[n] = true;
// 开始广度优先遍历
while (!q.empty()) {
// 取出当前要访问的节点的信息
int num = q.front().first;
int step = q.front().second;
q.pop();
// 如果当前节点的值为0,说明已经有了有效的最短路径
/*if (num == 0)
return step;*/
// 根据图的连接关系将周围节点放入队列中
for (int i = 1; ; i++) {
int a = num - i*i;
if (a < 0)
break;
if (a == 0)
return step + 1; // 这一步优化的是,不需要等到将节点从队列中取出再返回结果,我们检测到下一个节点是0,其实就可以返回想要的结果了
// 检测到下一节点没有访问过才才进行入队和visit置true操作,放置重复节点的多余路径查询
if (!visited[a]) {
q.push(make_pair(a, step + 1));
visited[a] = true;
}
}
}
throw invalid_argument("No Solution.");
}
//利用动态规划的方法来求解该题
// 思路: n最小完全平方数个数=min((n-1)最小完全平方数个数+1,(n-4)最小完全平方数个数+1,......,(n-i*i)最小完全平方数个数+1)
class Solution4 {
private:
vector<int> memo;
// 返回n的最小完全平方数个数
int _numSquares(int n) {
// 如果是完全平方数直接返回1
for (int i = 1;n-i*i>=0; i++) {
if (n == i*i)
return 1;
}
// 递归
if (memo[n] == 0) {
int res = INT_MAX;
for (int i = 1; n - i*i > 0; i++) {
res = min(res, _numSquares(n - i*i) + 1);
}
memo[n] = res;
}
return memo[n];
}
public:
int numSquares(int n) {
assert(n >= 1);
memo = vector<int>(n + 1, 0);
return _numSquares(n);
}
};
};
/**
* 127.单词接龙
* */
// 思路: 其实这题的程序大体框架还是沿用上一题,抽象成图,然后利用广度优先遍历求解最短路径
// 然而,这一题和上一题的不同点可能就是节点的定义和节点之间如何根据定义进行搜索链接
class Solution8 {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
// 首先判断一下直接返回0的情况
if (beginWord == endWord || wordList.size() == 0)
return 0;
for (int i = 0; i < wordList.size(); i++) {
if (wordList[i] == endWord)
break;
if (i == wordList.size() - 1)
return 0;
}
// 广度优先之前的一些辅助数据结构
queue< pair<string, int> > q;
q.push(make_pair(beginWord, 1));
unordered_set<string> visitWait;
for (int i = 0; i < wordList.size(); i++) {
visitWait.insert(wordList[i]);
}
// 开始广度优先遍历
while (!q.empty()) {
// 取出队列的头数据
string str = q.front().first;
int step = q.front().second;
q.pop();
// 在候选字符串中查找能链接的下一点
for (int i = 0; i < str.size(); i++) {
for (int j = 0; j < 26; j++) {
char match = 'a' + j;
if (match != str[i]) {
string str_tmp = str;
str_tmp[i] = match;
// 找到终点
if (str_tmp == endWord)
return step + 1;
// 不是终点的下一连接点
if (visitWait.find(str_tmp) != visitWait.end()) {
q.push(make_pair(str_tmp, step + 1));
visitWait.erase(str_tmp); // 一定要抹除,不然会超时
}
}
}
}
}
return 0;
}
};
/**
* 347.前k个高频元素
* */
// 思路: 维护一个k数据长的优先队列,如果后来的数据的频率高于优先队列中的最小频率,那么替换,最终的优先队列中的数值就是我们的结果,复杂度为o(nlogk)
class Solution9 {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// <数值,频率>
unordered_map<int, int> freq;
for (int i = 0; i < nums.size(); i++) {
freq[nums[i]]++;
}
// 开始维护K长的优先队列,pair<频率,数值>
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++) {
// 队列长度已到达k
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;
}
};
/**
* 23.合并K个排序链表
* */
// 思路: K段的并归排序和2段的并归排序的区别只是每一次待比较的数据是k个,然后选出最小的一个放置到最终的数据段中,这个k个的数据比较我们可以通过调用优先队列辅助完成
// 剩下的我们要做的只是不断取队列中最小元素的同时维护好队列就可,遍历完所有的数据之后,程序就可以返回并退出
class Solution10 {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
// 创建我们自定义的优先队列
priority_queue<ListNode*, vector<ListNode*>, function<bool(ListNode*, ListNode*)>> pq(myCmp2);
// 辅助的一些变量
ListNode* dummy = new ListNode(0);
ListNode* curNode = dummy;
// 维护一个K个数据的优先队列
for (int i=0; i < lists.size(); i++) {
if(lists[i])
pq.push(lists[i]);
}
// 开始遍历
while (!pq.empty()) {
// 取出优先队列头节点
ListNode* nowNode = pq.top();
pq.pop();
// 连接进最终的输出
curNode->next = nowNode;
curNode = curNode->next;
// 维护好优先队列,这里能这么做是因为链表是已经排好序的
if (nowNode->next)
pq.push(nowNode->next);
}
return dummy->next;
}
static bool myCmp2(ListNode* a, ListNode* b) {
return (a->val) > (b->val);
}
};