56. 合并区间
题目描述
以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
提示:
1 <= intervals.length <= 104
intervals[i].length == 2
0 <= starti <= endi <= 104
题解
贪心算法解决。这题和 435. 无重叠区间 很像,参考我那题的 笔记 ,可以采用类似的解法:
首先,将这些区间按照左边界从小到大排序。接着,尽量将下一个区间“纳入”当前区间,判断方法也很直观: 下一个区间的左边界 不大于 当前区间的右边界 ,就可以将其纳入当前区间。否则将下一个区间作为新的区间,计数。
代码(C++)
vector<vector<int>> merge(vector<vector<int>> &intervals)
{
// 贪心算法:先按左边界排序,再依次合并
auto cmp = [](const vector<int> &a, const vector<int> &b) {
return a[0] < b[0];
};
sort(intervals.begin(), intervals.end(), cmp);
vector<vector<int>> res;
int left = intervals[0][0], right = intervals[0][1];
for (int i = 0; i < intervals.size(); ++i) {
if (intervals[i][0] > right) {
res.emplace_back(vector<int>{left, right});
left = intervals[i][0], right = intervals[i][1];
} else
right = max(right, intervals[i][1]);
if (i == intervals.size() - 1)
res.emplace_back(vector<int>{left, right});
}
return res;
}
738. 单调递增的数字
题目描述
当且仅当每个相邻位数上的数字 x
和 y
满足 x <= y
时,我们称这个整数是单调递增的。
给定一个整数 n
,返回 小于或等于 n
的最大数字,且数字呈 单调递增 。
示例 1:
输入: n = 10
输出: 9
示例 2:
输入: n = 1234
输出: 1234
示例 3:
输入: n = 332
输出: 299
提示:
0 <= n <= 109
题解
贪心算法解决。结合所给示例,我们不难想出将 n
转化为单调递增数的一个简单思路:从最低位开始,如果当前位(第 i
位)数字比上一位(第 i + 1
位)数字小,即满足单调递增,则数字不变;否则,第 i
位数字减一、之前各位数字变为9。
例如:332 ➡️ 329 ➡️ 299
因此,我们可以这样将数字从后往前“扫一遍”,记录最后要将数字变为9的起始位置,之后将它们都变为9即可。
代码(C++)
int monotoneIncreasingDigits(int n)
{
string strN = to_string(n);
// 从后往前逐位确定
int flag = strN.size(); // 结果末尾连续9的起始位
for (int i = strN.size() - 1; i > 0; --i) {
if (strN[i - 1] > strN[i]) {
strN[i - 1]--;
flag = i;
}
}
for (int i = flag; i < strN.size(); ++i)
strN[i] = '9';
return stoi(strN);
}
968. 监控二叉树
题目描述
给定一个二叉树,我们在树的节点上安装摄像头。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。
示例 1:
输入:[0,0,null,0,0]
输出:1
解释:如图所示,一台摄像头足以监控所有节点。
示例 2:
输入:[0,0,null,0,null,0,null,null,0]
输出:2
解释:需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。
提示:
- 给定树的节点数的范围是
[1, 1000]
。 - 每个节点的值都是 0。
题解
比较巧妙的贪心算法利用。结合给出的示例,我们不难想出一种最节省摄像头的方案:如果如果当前节点、子节点、父节点都没有被监控范围覆盖,则在当前节点添加一个摄像头即可。例如,要将监控下面这棵单侧树的所有6个节点:
0
/
0
/
0
/
0
/
0
/
0
只需要在第1和第5个节点处添加摄像头即可(用 ‘2’ 表示摄像头):
0
/
2
/
0
/
0
/
2
/
0
同时可以发现,我们总是不需要在叶子节点安装摄像头的(除非是平凡树),即应该从叶子节点开始向上贪心地尽可能少地安装摄像头。因此,应当需要一种自下而上的算法,自然联想到后序遍历。考虑递归方法解决,基本框架应该是:
int traversal(TreeNode* cur) {
if (终止条件) return ;
int left = traversal(cur->left); // 左
int right = traversal(cur->right); // 右
逻辑处理 // 中
return ;
}
返回一个整数,表示当前节点此时的状态——无外乎3种:
- 未覆盖(
0
) - 有覆盖但不是摄像头(
1
) - 是摄像头(
2
)
根据子节点的状态,可以确定当前节点是否安装摄像头:
class Solution
{
private:
int count = 0;
/// @brief 从叶子节点自下而上后序遍历,确定摄像摄像头的位置
/// @param cur
/// @return 当前节点未覆盖(0),被覆盖但无摄像头(1),有摄像头(2)
int traverse(TreeNode *cur)
{
if (!cur)
return 1; // 空节点认为被覆盖但无摄像头
int left = traverse(cur->left); // 左
int right = traverse(cur->right); // 右
// 中
if (!left || !right) // 左右孩子中有没被覆盖的
{
count++;
return 2; // 放摄像头
}
else if (left == 2 || right == 2) // 左右孩子中有摄像头
return 1; // 当前节点肯定被覆盖,没必要放摄像头
else // 左右孩子都被覆盖但都不是摄像头
return 0; // 当前节点(暂时)不覆盖,交给父节点解决
}
public:
int minCameraCover(TreeNode *root)
{
if (!traverse(root))
return count + 1;
else
return count;
}
};
⚠️ 注意两处细节:
- 空节点被认为是“被覆盖但无摄像头”,因为这样才能确保其父节点(即叶子节点)不会安装多余的摄像头
- 最后要检查一下根节点
root
是否被覆盖,如没有还需要给它自己再安一个摄像头