150m. 逆波兰表达式求值
方法一:栈
用时:28m10s
思路
遇到数字就入栈,遇到运算符就出栈两个数字,将两个数字的运算结果入栈。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<long long> s;
long long n1 = 0;
long long n2 = 0;
for (string& token : tokens) {
if (token != "+" && token != "-" && token != "*" && token != "/") s.push(stoll(token));
else {
n2 = s.top();
s.pop();
n1 = s.top();
s.pop();
if (token == "+") s.push(n1 + n2);
else if (token == "-") s.push(n1 - n2);
else if (token == "*") s.push(n1 * n2);
else s.push(n1 / n2);
}
}
return s.top();
}
};
看完讲解的思考
无。
代码实现遇到的问题
又是数值溢出问题…
239h. 滑动窗口最大值
方法一:暴力解法
用时:13m51s
思路
遍历每个滑动窗口,然后每次都统计一次最大值。暴力解法在力扣上超出时间限制了…
- 时间复杂度: O ( n k ) O(nk) O(nk),n是数组的长度,k是滑动窗口的大小。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int size = nums.size();
int left = 0;
int right = k - 1;
vector<int> res(size - k + 1, 0);
while (right < size) {
int maxNum = nums[left];
for (int i = left + 1; i <= right; ++i) {
if (maxNum < nums[i]) maxNum = nums[i];
}
res[left++] = maxNum;
++right;
}
return res;
}
};
方法二:单调队列
用时:32m25s
思路
自定义一个单调队列类,用deque实现,维护deque中的元素,使其从队头到队尾单调递减。成员函数:
- push:当队尾元素小于新增元素时,将队尾元素弹出,直到队尾元素大于等于新增元素,再将新增元素从队尾入队。
- pop:弹出队头元素。
- front:返回队头元素。
用单调队列q来维护滑动窗口。滑动窗口移动过程,当左边界移除窗口的元素与q队头元素一致时,执行pop操作,然后将窗口右边界新增的元素push进q中。由于q是单调队列,所以q的队头一定是当前滑动窗口的最大值。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( k ) O(k) O(k)。
C++代码
class MyQueue {
public:
MyQueue() {
this->d = new deque<int>();
}
void push(int val) {
while (!this->d->empty() && this->d->back() < val) this->d->pop_back();
this->d->push_back(val);
}
void pop() {
this->d->pop_front();
}
int front() {
return this->d->front();
}
private:
deque<int>* d;
};
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MyQueue q;
int size = nums.size();
int right = k - 1;
vector<int> res(size - k + 1, 0);
for (int i = 0; i <= right; ++i) q.push(nums[i]);
res[0] = q.front();
while (right < size - 1) {
if (nums[right++ - k + 1] == q.front()) q.pop();
q.push(nums[right]);
res[right - k + 1] = q.front();
}
return res;
}
};
看完讲解的思考
妙啊单调队列。
代码实现遇到的问题
无。
347m. 前 K 个高频元素
方法一:优先队列(堆)
用时:50m10s
思路
- 统计每个元素出现的频次,用哈希表记录。
- 创建一个小顶堆的优先队列,由于存储的数据类型是pair(unordered_map中每个键值对是用pair类型存储),需要自定义优先队列的比较函数
MyComparision
,根据pair.second(元素出现的频次)来比较大小。将哈希表的键值对保存在优先队列中,优先队列的大小限制为k,当元素数量超出k时,弹出队首元素,由于是小顶堆的优先队列,队首元素是出现频次最少的元素。将全部键值对遍历完后,优先队列中留下来的k个元素即为出现频次最高的k个元素。 - 将优先队列转化为vector返回。由于先弹出的元素是出现频次小的元素,所以要逆序保存至数组中,这样得到的数组才是出现频次由高到低的前k个元素(不过该题对结果的顺序并无要求)。
- 时间复杂度: O ( n + n log k + k ) = O ( n log k ) O(n+n\log{k}+k) = O(n\log{k}) O(n+nlogk+k)=O(nlogk)。
- 空间复杂度: O ( 2 n + k + k ) = O ( n ) O(2n+k+k) = O(n) O(2n+k+k)=O(n)。
C++代码
class Solution {
public:
class MyComparision {
public:
// 小顶堆的比较函数
bool operator()(const pair<int, int>& left, const pair<int, int>& right) {
return left.second > right.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
// 统计每个元素出现的频次
unordered_map<int, int> hashMap;
for (int& n : nums) ++hashMap[n];
// 将元素和对应的频次保存到小顶堆的优先队列中,只保留k个,当大于k个元素时,将队首元素(频次最少的元素)弹出
// 优先队列,存储的数据类型是pair<int, int>,使用vector<pair<int, int>>来存储,使用MyComparison来比较元素优先级
// unordered_map的键值对是pair类型
priority_queue<pair<int, int>, vector<pair<int, int>>, MyComparision> q;
for (unordered_map<int, int>::iterator it = hashMap.begin(); it != hashMap.end(); ++it) {
q.push(*it);
if (q.size() > k) q.pop();
}
// 优先队列中留下来的k个元素即是出现频率前k高的元素,将其存储至vector中返回
vector<int> res(k, 0);
// 最先弹出来的是出现频次最小的,所以逆序保存至vector
for (int i = k - 1; i >= 0; --i) {
res[i] = q.top().first;
q.pop();
}
return res;
}
};
看完讲解的思考
之前没学过堆和优先队列,正好去学习了一波堆的数据结构。
代码实现遇到的问题
无。
144. 二叉树的前序遍历
方法一:递归法
用时:6m6s
思路
先记录当前节点的元素,再遍历左节点,再遍历右节点。递归结束的条件是当前节点为空。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
traversal(root, res);
return res;
}
void traversal(TreeNode* node, vector<int>& v) {
if (node == nullptr) return;
v.push_back(node->val);
traversal(node->left, v);
traversal(node->right, v);
}
};
方法二:迭代法(前中后序遍历统一)
用时:10m23s
思路
迭代法本质上与递归法一致,只是递归法是隐式地使用了栈,迭代法是显式地使用栈。
先将根节点入栈,前序遍历的顺序是“中左右”,所以在循环中,先将栈顶的节点弹出并记录元素(中),然后将弹出的节点的右节点入栈,再将弹出的节点的左节点入栈。因为左节点后入栈,所以在下次循环中,左节点位于栈顶,先出栈并被记录,所以顺序就是“中左右”。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> sta;
vector<int> res;
if (root == nullptr) return res;
sta.push(root);
while (!sta.empty()) {
TreeNode* node = sta.top();
sta.pop();
res.push_back(node->val);
if (node->right != nullptr) sta.push(node->right);
if (node->left != nullptr) sta.push(node->left);
}
return res;
}
};
看完讲解的思考
无。
代码实现遇到的问题
无。
94. 二叉树的中序遍历
方法一:递归
用时:3m1s
思路
先遍历左节点,再记录当前节点的元素,再遍历右节点。递归结束的条件是当前节点为空。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
traversal(root, res);
return res;
}
void traversal(TreeNode* node, vector<int>& v) {
if (node == nullptr) return;
traversal(node->left, v);
v.push_back(node->val);
traversal(node->right, v);
}
};
方法二:迭代法
思路
迭代法与递归法本质上是一样的。中序遍历的迭代法的逻辑与前序遍历的不同。
中序遍历的顺序是“左中右”。先是不断将当前节点入栈,然后将当前节点更新成左节点,直到当前节点为空时,记录并弹出栈顶节点,将弹出节点的值添加到结果数组中,此时的值即为最左下边的节点,然后再将弹出节点的右节点入栈。语言很难描述清楚,建议根据代码实际模拟一遍。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> sta;
TreeNode* cur = root;
while (cur != nullptr || !sta.empty()) {
if (cur != nullptr) {
sta.push(cur);
cur = cur->left;
} else {
cur = sta.top();
sta.pop();
res.push_back(cur->val);
cur = cur->right;
}
}
return res;
}
};
方法三:迭代法(前中后序遍历统一)
思路
方法二的迭代法的代码逻辑与前序遍历的迭代法的代码逻辑相差较大,可不可以像递归法一样,写出一种统一的代码逻辑,只用修改一下顺序。
前序遍历由于顺序是“中左右”,所以在迭代法中是直接先记录当前节点,再将子节点入栈;而中序遍历和后续遍历虽然遍历到了当前节点,但是第一次遍历到的时候并不记录,而是要先去记录子节点,所以当第一次遍历到当前节点时在后面添加一个空节点作为标志,等子节点记录完回到当前节点时,看到空节点标志再将当前节点记录。
中序遍历顺序是“左中右”,所以对于当前节点,先弹出,然后将右节点入栈,再将当前节点和一个空节点入栈,最后将左节点入栈。
后续遍历顺序是“左右中”,所以对于当前节点,先将一个空节点入栈,再将右节点入栈,最后将左节点入栈。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
if (root == nullptr) return res;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {
st.pop();
if (node->right != nullptr) st.push(node->right);
st.push(node);
st.push(nullptr);
if (node->left != nullptr) st.push(node->left);
} else {
st.pop();
node = st.top();
st.pop();
res.push_back(node->val);
}
}
return res;
}
};
看完讲解的思考
迭代法有点绕,之后得多敲几遍。
代码实现遇到的问题
无。
145e. 二叉树的后序遍历
方法一:递归
用时:59s
思路
跟前序和中序遍历差不多。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
traversal(root, res);
return res;
}
void traversal(TreeNode* node, vector<int>& v) {
if (node == nullptr) return;
traversal(node->left, v);
traversal(node->right, v);
v.push_back(node->val);
}
};
方法二:迭代法
思路
在前序遍历的迭代法中,可以能得到“中左右”顺序的结果,在前序遍历的迭代法的代码的基础上调整一下左右节点的入栈顺序,就能得到“中右左”顺序的结果,再将得到的结果翻转,即可得到“左右中”顺序的结果,即后序遍历的结果。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
if (root == nullptr) return res;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
res.push_back(node->val);
if (node->left != nullptr) st.push(node->left);
if (node->right != nullptr) st.push(node->right);
}
reverse(res.begin(), res.end());
return res;
}
};
方法三:迭代法(前中后序遍历统一)
思路
见上一题(94e)方法三。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
if (root == nullptr) return res;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != nullptr) {
st.push(nullptr);
if (node->right != nullptr) st.push(node->right);
if (node->left != nullptr) st.push(node->left);
} else {
st.pop();
node = st.top();
st.pop();
res.push_back(node->val);
}
}
return res;
}
};
看完讲解的思考
无。
代码实现遇到的问题
无。
最后的碎碎念
进入二叉树章节了,好多内容啊这一章。今天状态比前几天稍微好一点,但也只是“一点”,老是想着想着就去刷刷手机啥的,已经把知乎、小红书、微博卸载了,我还不信了。专注!专注!还题目是专注!