435. 无重叠区间
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意: 可以认为区间的终点总是大于它的起点。 区间 [1,2] 和 [2,3] 的边界相互"接触",但没有相互重叠。
示例 1:
- 输入: [ [1,2], [2,3], [3,4], [1,3] ]
- 输出: 1
- 解释: 移除 [1,3] 后,剩下的区间没有重叠。
示例 2:
- 输入: [ [1,2], [1,2], [1,2] ]
- 输出: 2
- 解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
示例 3:
- 输入: [ [1,2], [2,3] ]
- 输出: 0
- 解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
思路:
-
排序:
- 按照区间的右端进行排序。贪心算法的核心在于每一步都选择一个局部最优解,这里局部最优解就是选择结束时间最早的区间,这样可以为后续区间保留更多空间。
-
遍历区间:
- 选择第一个区间,记录其结束时间。
- 从第二个区间开始,逐个检查:
- 如果当前区间的起始时间小于前一个选择区间的结束时间,则说明重叠,需要移除当前区间。
- 如果当前区间的起始时间不小于前一个选择区间的结束时间,则说明不重叠,更新结束时间为当前区间的结束时间。
cpp
class Solution {
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if (intervals.empty()) return 0;
sort(intervals.begin(), intervals.end(), [](vector<int>& a, vector<int>& b) {
return a[1] < b[1];
});
int cur_end = INT_MIN;
int n = 0;
for(auto interval: intervals) {
if (interval[0] < cur_end) n++;
else cur_end = interval[1];
}
return n;
}
};
763.划分字母区间
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。
示例:
- 输入:S = "ababcbacadefegdehijhklij"
- 输出:[9,7,8] 解释: 划分结果为 "ababcbaca", "defegde", "hijhklij"。 每个字母最多出现在一个片段中。 像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。
提示:
- S的长度在[1, 500]之间。
- S只包含小写字母 'a' 到 'z' 。
思路:
要解决这个问题,可以使用贪心算法和双指针技巧:
- 遍历字符串:记录每个字符最后出现的位置。
- 划分片段:使用两个指针来确定每个片段的起始和结束位置。
-
- 使用两个指针
start
和end
来表示当前片段的开始和结束位置。 - 遍历字符串中的每个字符,更新当前片段的结束位置
end
为当前字符的最后出现位置。 - 如果当前索引
i
达到当前片段的结束位置end
,说明我们找到了一个完整的片段,将其长度加入结果列表中,并更新start
为下一个片段的开始位置。
- 使用两个指针
- 输出结果:将每个片段的长度加入结果列表中。
cpp
class Solution {
public:
vector<int> partitionLabels(string s) {
vector<int> nums(26), len;
for (int i = 0; i < s.size(); i++) {
nums[s[i] - 'a'] = i;
}
int start = 0, end = 0;
for (int i = 0; i < s.size(); i++) {
if (end < nums[s[i] - 'a']) {
end = nums[s[i] - 'a'];
}
if (i == end) {
len.push_back(end - start + 1);
start = end + 1;
}
}
return len;
}
};
56. 合并区间
给出一个区间的集合,请合并所有重叠的区间。
示例 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] 可被视为重叠区间。
- 注意:输入类型已于2019年4月15日更改。 请重置默认代码定义以获取新方法签名。
思路:
-
排序区间:
- 对区间按起始时间排序,确保处理区间时有序。
-
初始化结果:
- 检查输入是否为空,如果为空,直接返回空的结果。
- 初始化结果向量
result
,并将第一个区间加入其中。
-
合并区间:
- 遍历所有区间,如果当前区间的起始时间小于或等于结果向量中最后一个区间的结束时间,则说明它们重叠,更新结果向量中最后一个区间的结束时间。
- 如果不重叠,将当前区间加入结果向量。
cpp
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
if (intervals.empty()) return {};
sort(intervals.begin(), intervals.end());
vector<vector<int>> result;
result.push_back(intervals[0]);
for (int i = 1; i < intervals.size(); ++i) {
if (intervals[i][0] <= result.back()[1]) {
result.back()[1] = max(result.back()[1], intervals[i][1]);
} else {
result.push_back(intervals[i]);
}
}
return result;
}
};
738.单调递增的数字
给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。
(当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。)
示例 1:
- 输入: N = 10
- 输出: 9
示例 2:
- 输入: N = 1234
- 输出: 1234
示例 3:
- 输入: N = 332
- 输出: 299
说明: N 是在 [0, 10^9] 范围内的一个整数。
思路:
-
字符串处理:
- 将整数 N 转换为字符串
str
,方便进行位数操作。
- 将整数 N 转换为字符串
-
从后向前检查:
- 从最后一位开始向前遍历,找到第一个不满足递增条件的位置
marker
,即str[i] < str[i - 1]
。
- 从最后一位开始向前遍历,找到第一个不满足递增条件的位置
-
递减处理:
- 将
marker
前一位的字符减1,确保这一位之前的数字仍然满足递增条件。
- 将
-
后续处理:
- 将
marker
后面的所有位设置为 '9',确保最大化这些位置的数字。
- 将
-
结果返回:
- 将处理后的字符串
str
转换为整数返回。
- 将处理后的字符串
cpp
class Solution {
public:
int monotoneIncreasingDigits(int n) {
string str = to_string(n);
int mark = str.size();
for (int i = str.size() - 1; i > 0; i--) {
if (str[i] < str[i - 1]) {
str[i - 1]--;
mark = i;
}
}
for (int j = mark; j < str.size(); j++) str[j] = '9';
return stoi(str);
}
};
968.监控二叉树
给定一个二叉树,我们在树的节点上安装摄像头。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。
示例 1:
- 输入:[0,0,null,0,0]
- 输出:1
- 解释:如图所示,一台摄像头足以监控所有节点。
示例 2:
- 输入:[0,0,null,0,null,0,null,null,0]
- 输出:2
- 解释:需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。
提示:
- 给定树的节点数的范围是 [1, 1000]。
- 每个节点的值都是 0。
思路:
可以将每个节点标记为三种状态之一:
- 该节点已被其子节点监控。
- 该节点有一个摄像头。
- 该节点未被监控。
通过递归地遍历树,我们可以决定在每个节点上是否需要安装摄像头,并计算所需的最小摄像头数量。
-
- 对于每个节点,先递归处理其左子节点和右子节点。
- 如果左子节点或右子节点未被监控 (
0
),则当前节点需要一个摄像头 (1
),并增加摄像头计数。 - 如果左子节点或右子节点有摄像头 (
1
),则当前节点被监控 (2
)。 - 否则,当前节点未被监控 (
0
)。
-
递归处理根节点:
- 如果根节点未被监控 (
0
),需要在根节点安装摄像头
- 如果根节点未被监控 (
cpp
class Solution {
public:
int minCameraCover(TreeNode* root) {
int cameras = 0;
if (dfs(root, cameras) == 0) { // 根节点未被监控
cameras++;
}
return cameras;
}
private:
// 0 - 该节点未被监控
// 1 - 该节点有摄像头
// 2 - 该节点已被其子节点监控
int dfs(TreeNode* node, int& cameras) {
if (!node) return 2; // 空节点视为已被监控
int left = dfs(node->left, cameras);
int right = dfs(node->right, cameras);
if (left == 0 || right == 0) {
cameras++;
return 1; // 当前节点需要一个摄像头
}
if (left == 1 || right == 1) {
return 2; // 当前节点已被其子节点监控
}
return 0; // 当前节点未被监控
}
};