LeetCode刷题总结:(4)栈和队列的相关问题

关于栈的应用一般是找最近对应关系这一类的问题,关于队列的问题一般是作为广度优先搜索的辅助数据结构这样。总之,一般来说,关于栈和队列的应用一般都是作为用于解决其他问题的一种辅助数据结构这样。

 

20. 有效的括号

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

有效字符串需满足:

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

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

// 思路: 如果当前的符号是前一半,那么推入栈,如果是后一半就找最近的匹配括号,如果是就推出,遍历下一个,如果不匹配那么久失败退出
class Solution {
public:
	bool isValid(string s) {

		stack 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 match;

				switch (s[i])
				{
					case ')':
						match = '(';
						break;

					case ']':
						match = '[';
						break;

					case '}':
						match = '{';
						break;

					default:
						break;
				}

				char c = stack.top();
				stack.pop();

				if (c != match)
					return false;
			}
		}

		if (stack.size() != 0)
			return false;


		return true;

	}
};

 

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 Solution1 {
public:
	int evalRPN(vector& tokens) {

		stack 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;
	}
};

 

102. 二叉树的层次遍历

给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。

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

    3
   / \
  9  20
    /  \
   15   7

返回其层次遍历结果:

[
  [3],
  [9,20],
  [15,7]
]
// 思路: 还是用辅助的队列完成整个数的层序遍历,只不过分析后发现还要存储节点的层序值,所以将queue中的数据结构考虑为一个pair对
class Solution {
public:
	vector> levelOrder(TreeNode* root) {

		if (root == NULL)
			return vector>();

		vector> res;
		queue< pair > 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
			if (level == res.size()) {
				res.push_back(vector());
			}
			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;
	}
};

 

107. 二叉树的层次遍历 II

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

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

    3
   / \
  9  20
    /  \
   15   7

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

[
  [15,7],
  [9,20],
  [3]
]
// 思路: 套用上一题的思路,只不过最终输出的形式需要倒置一下
class Solution1 {
public:
	vector> levelOrderBottom(TreeNode* root) {

		if (root == NULL)
			return vector>();

		vector> res_tmp;

		queue< pair > 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
			if (res_tmp.size() == level)
				res_tmp.push_back(vector());
			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> res;
		for (int i = 0; i < res_tmp.size(); i++) {
			res.push_back(vector());
			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. 二叉树的锯齿形层次遍历

给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

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

    3
   / \
  9  20
    /  \
   15   7

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

[
  [3],
  [20,9],
  [15,7]
]
// 思路: 其实层序遍历的过程大体上是一样的,只不过在最终输出的形式上略有差别,以符合回形针的读取形式
class Solution2 {
public:
	vector> zigzagLevelOrder(TreeNode* root) {

		if (root == NULL)
			return vector>();

		queue< pair > q;
		q.push(make_pair(root, 0));
		vector> res;

		while (!q.empty()) {

			TreeNode* node = q.front().first;
			int level = q.front().second;
			q.pop();

			if (level == res.size())
				res.push_back(vector());
			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> 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. 二叉树的右视图

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

示例:

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

   1            <---
 /   \
2     3         <---
 \     \
  5     4       <---
// 思路: 其实这题更简单,还是本质上还是层序遍历,只不过每次放置子节点的时候可以先放置右节点,完了将每一层的第一个数据取出即可
class Solution3 {
public:
	vector rightSideView(TreeNode* root) {

		if (root == NULL)
			return vector();

		queue< pair > q;
		q.push(make_pair(root, 0));
		vector> res;

		while (!q.empty()) {

			TreeNode* node = q.front().first;
			int level = q.front().second;
			q.pop();

			if (level == res.size())
				res.push_back(vector());
			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 result;
		for (int i = 0; i < res.size(); i++)
			result.push_back(res[i][0]);

		return result;
	}
};

 

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) {

		// 既然是求图的最短路径,那么自然要用到队列的辅助数据结构
		queue< pair > q;
		q.push(make_pair(n, 0));
		// 辅助的访问记录数据,这里为n+1个数据是因为0,,...,n有n+1个数据
		vector 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.");
	}
};

 

127. 单词接龙

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

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

说明:

  • 如果不存在这样的转换序列,返回 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 Solution1 {
public:
	int ladderLength(string beginWord, string endWord, vector& 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 > q;
		q.push(make_pair(beginWord, 1));
		unordered_set 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个高频元素

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

例如,

给定数组 [1,1,1,2,2,3] , 和 k = 2,返回 [1,2]

注意:

  • 你可以假设给定的 总是合理的,1 ≤ k ≤ 数组中不相同的元素的个数。
  • 你的算法的时间复杂度必须优于 O(n log n) , 是数组的大小。
// 思路: 维护一个k数据长的优先队列,如果后来的数据的频率高于优先队列中的最小频率,那么替换,最终的优先队列中的数值就是我们的结果,复杂度为o(nlogk)
class Solution2 {
public:
	vector topKFrequent(vector& nums, int k) {

		// <数值,频率>
		unordered_map freq;
		for (int i = 0; i < nums.size(); i++) {
			freq[nums[i]]++;
		}

		// 开始维护K长的优先队列,pair<频率,数值>
		priority_queue< pair, vector>, greater> > pq;
		for (unordered_map::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 res;
		while (!pq.empty()) {
			res.push_back(pq.top().second);
			pq.pop();
		}

		return res;
	}
};

 

23. 合并K个排序链表

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

示例:

输入:
[
  1->4->5,
  1->3->4,
  2->6
]
输出: 1->1->2->3->4->4->5->6
// 思路: K段的并归排序和2段的并归排序的区别只是每一次待比较的数据是k个,然后选出最小的一个放置到最终的数据段中,这个k个的数据比较我们可以通过调用优先队列辅助完成
// 剩下的我们要做的只是不断取队列中最小元素的同时维护好队列就可,遍历完所有的数据之后,程序就可以返回并退出
class Solution3 {
public:
	ListNode* mergeKLists(vector& lists) {

		// 创建我们自定义的优先队列
		priority_queue, function> 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);
	}
};

 

 

你可能感兴趣的:(LeetCode刷题记录)