799.香槟塔
题目链接:799.champagne-tower
解法:
模拟法:模拟倒香槟的过程,当有溢出时,将溢出的部分平均倒到下一层的相邻的两个杯子中。每一个杯子保留的是溢出之前的值,所以最后返回结果时,通过 row=query_row,glass=query_glass来取对应杯子装的香槟,并和1.0比较求最小值。
虽然一个杯子需要根据 row和column来确定位置,但这种方法使用了滚动数组,也就是一位数组,而不是二维数组,减低了空间复杂度。参考资料:模拟法题解
动态规划:这种解法其实和上面的模拟法思路一样,叫动态规划有点牵强。使用的二维数组,可以更清晰的确定杯子的位置,但是提高了空间复杂度。
动态规划的写法,C++容易出错,因为初始化二维数组时,要给定合适的行数和列数。参考资料:动态规划解法
边界条件:无
时间复杂度:模拟法O(query_row2)。使用了两层for 循环。
空间复杂度:模拟法O(query_row),使用了数组。
// 模拟倒香槟
class Solution {
public:
double champagneTower(int poured, int query_row, int query_glass) {
vector<double> row = {(double)poured};
for (int i=1; i<=query_row; i++) {
// 把下一行的杯子初始化为0
vector<double> nextRow(i+1, 0.0);
for (int j=0; j<row.size(); j++) {
double volume = row[j];
// 如果当前的杯子装的香槟大于1杯,则把溢出的平方到下一层的左右杯
if (volume > 1) {
nextRow[j] += (volume - 1) / 2;
nextRow[j+1] += (volume - 1) / 2;
}
}
row = nextRow;
}
// 如果该杯子分配的大于1杯,则最多装1杯
return min(1.0, row[query_glass]);
}
};
// dp
class Solution {
public:
double champagneTower(int poured, int query_row, int query_glass) {
// 初始化时,行和列至少为 query_row+2,不然报错
// 因为row是从0开始,那么query_row就有query_row+1行,而下面递推公式有i+1,那么会达到query_row+2
vector<vector<double>> row(query_row+2, vector<double>(query_row+2, 0.0));
row[0][0] = poured;
for (int i=0; i<=query_row; i++) {
for (int j=0; j<=i; j++) {
if (row[i][j] <= 1) continue;
row[i+1][j] += (row[i][j] - 1) / 2;
row[i+1][j+1] += (row[i][j] - 1) / 2;
}
}
return min(1.0, row[query_row][query_glass]);
}
};
2096.从二叉树一个节点到另一个节点每一步的方向
题目链接:step-by-step-directions-from-a-binary-tree-node-to-another
解法:
这个题真的是题目长,题解长,代码也长。没啥可总结的,只能看题解才有思路。参考题解:深度优先搜索
边界条件:无
时间复杂度:O(n),其中 n为树中节点数目。深度优先搜索与维护路径的时间复杂度均为 O(n)。
空间复杂度:O(n),即为深度优先搜索的栈空间开销和根节点到起点终点路径字符串的空间开销。
/**
* 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:
string getDirections(TreeNode* root, int startValue, int destValue) {
// 哈希表记录每个节点的父节点
unordered_map<TreeNode*, TreeNode*> fa;
TreeNode* s = nullptr;
TreeNode* t = nullptr;
// dfs维护哈希表,并得到起点和终点
function<void(TreeNode*)> dfs = [&] (TreeNode* curr) {
if (curr->val == startValue) s = curr;
if (curr->val == destValue) t = curr;
if (curr->left) {
fa[curr->left] = curr;
dfs(curr->left);
}
if (curr->right) {
fa[curr->right] = curr;
dfs(curr->right);
}
};
dfs(root);
// 定义函数,自下而上的求出起点和终点到根节点的路径,并翻转
function<string(TreeNode*)> path = [&] (TreeNode* curr) {
string res;
while (curr != root) {
TreeNode* par = fa[curr];
if (curr == par->left) res.push_back('L');
if (curr == par->right) res.push_back('R');
curr = par;
}
reverse(res.begin(), res.end());
return res;
};
string path1 = path(s);
string path2 = path(t);
// 计算从root到最近公共祖先的公共前缀部分,并删除
int l1 = path1.size();
int l2 = path2.size();
int i = 0;
while (i < min(l1, l2)) {
if (path1[i] == path2[i]) {
i++;
} else {
break;
}
}
// 替换path1为'U',而直接拼接path2的部分
string finalPath(l1-i, 'U');
finalPath.append(path2.substr(i, l2-i));
return finalPath;
}
};
253.会议室 II
题目链接:253. 会议室 ||
解法:
使用优先队列(最小堆),先对所有的区间按照开始时间进行排序,再使用小根堆保存每个会议的结束时间,最早的结束时间排在前面。
遍历时,如果小根堆中最早结束的会议,结束时间小于等于当前的会议的开始时间,那么把最早结束的会议弹出,弹入当前会议,表示不需要开新会议室,而是占用之前的。最后,堆的大小表示了现在已经占用了多少个会议室。
解法参考:排序+最小堆
边界条件:无
时间复杂度:O(nlogn)
空间复杂度:O(n)
class Solution {
public:
int minMeetingRooms(std::vector<std::vector<int>>& intervals) {
// 对会议按开始时间排序
sort(intervals.begin(), intervals.end(), [](vector<int>& a, vector<int>& b) {
return a[0] < b[0];
});
// 使用优先队列(最小堆)来存储会议的结束时间
priority_queue<int, vector<int>, greater<int>> minHeap;
for (vector<int>& interval : intervals) {
// 如果最早结束的会议可以结束掉,让新会议用它的会议室
if (!minHeap.empty() && minHeap.top()<=interval[0]) {
minHeap.pop();
}
// 将新会议的结束时间加入堆中
minHeap.push(interval[1]);
}
// 堆的大小即为所需的会议室数量
return minHeap.size();
}
};