力扣题目链接(opens new window)
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意: 可以认为区间的终点总是大于它的起点。 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
示例 1:
示例 2:
示例 3:
这个问题的关键是如何在保留尽可能多的区间的同时,消除所有的重叠。解决这个问题的一个有效策略是使用贪心算法。以下是解题的基本思路和步骤:
排序:首先,将所有区间按照结束时间进行排序。这一步是非常关键的,因为你希望尽可能地让更多的区间能够适应,这就需要关注区间的结束时间,给其他的区间留出更多的空间。例如,如果有区间 [1,3] 和 [2,4],保留 [1,3] 会更有意义,因为它结束得更早,留给后面的区间更多的空间。
寻找重叠:接下来,你需要检查有哪些区间是重叠的。从排序后的第一个区间开始,检查每个区间是否与前一个区间重叠。如果当前区间的起始时间小于前一个区间的结束时间,那么它们是重叠的。
移除重叠:每当你找到重叠的区间时,都需要进行一个决定:保留哪一个区间?根据我们的贪心策略,我们应该保留结束时间更早的那个区间,因为这样可以为后续的区间留出更多的空间。因此,每当我们遇到重叠时,我们选择结束时间更晚的区间进行移除。
计数移除的区间:为了知道需要移除多少个区间,我们需要对每一次移除进行计数。这样,当我们遍历完所有的区间后,就知道了为了避免重叠需要移除的区间总数。
结果:遍历并比较所有区间之后,你就得到了需要移除的最少区间数以保证剩余区间互不重叠。
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
# 如果intervals为空,直接返回0
if not intervals:
return 0
# 根据区间的结束时间对所有区间进行排序
intervals.sort(key=lambda x: x[1])
# 初始化已选择的区间的结束时间为负无穷大
end = float('-inf')
# 初始化移除的区间数量为0
removed = 0
# 遍历所有区间
for interval in intervals:
# 如果当前区间的开始时间大于已选择的区间的结束时间
# 则选择这个区间,更新end为当前区间的结束时间
if interval[0] >= end:
end = interval[1]
else:
# 否则,需要移除这个区间,增加移除的区间数量
removed += 1
# 返回移除的区间数量
return removed
以示例1为例,逐步解释代码的运行过程。
示例1:
输入: intervals = [[1,2], [2,3], [3,4], [1,3]]
1. 排序: 首先,我们对区间按照结束点进行排序:
排序后: intervals = [[1,2], [1,3], [2,3], [3,4]]
2. 初始化变量:
removed = 0 (记录移除的区间数量) end = -∞ (记录当前已确定的无重叠区间的结束点)
3. 遍历排序后的区间:
第1个区间 [1,2]:
1 (区间开始点) >= -∞ (end)
, 所以这个区间被接受。end
为 2 (end
的新值为当前区间的结束点)。第2个区间 [1,3]:
1 (区间开始点) < 2 (end)
, 所以这个区间与前一个区间重叠。removed
的值为1(表示移除一个区间)。第3个区间 [2,3]:
2 (区间开始点) >= 2 (end)
, 所以这个区间被接受。end
为 3 (end
的新值为当前区间的结束点)。第4个区间 [3,4]:
3 (区间开始点) >= 3 (end)
, 所以这个区间被接受。end
为 4 (end
的新值为当前区间的结束点)。4. 结果: 遍历结束后, removed
的值为1, 表示我们需要移除1个区间来确保剩下的区间没有重叠。
输出: 1
力扣题目链接
字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。
示例:
提示:
我们使用一个具体的例子来详细解释代码的运行过程。假设我们有字符串 s = "abacccd"
。
首先,我们创建一个字典 last_occurrence
来记录每个字符在字符串中的最后出现位置。
s = "abacccd" last_occurrence = {'a': 2, 'b': 1, 'c': 6, 'd': 7}
接下来,我们初始化片段的开始和结束位置,以及结果列表。
start = 0 end = 0 result = []
当 idx=0
时, char='a'
:
end = max(0, 2) = 2
,因为字符 'a' 的最后出现位置是 2。当 idx=1
时, char='b'
:
end = max(2, 1) = 2
,因为字符 'b' 的最后出现位置是 1,但我们已经有一个更大的结束位置。当 idx=2
时, char='a'
:
end
不变,仍然是2。idx
等于 end
,这意味着从当前的 start
(即0)到 end
(即2)是一个片段。因此我们记录这个片段的长度为 3
(因为 2 - 0 + 1 = 3
) 并添加到结果列表。start
为下一个索引,即 3
。当 idx=3
时, char='c'
:当 idx=3
时, char='c'
:
end = max(2, 6) = 6
,因为字符 'c' 的最后出现位置是 6。以此类推,直到遍历完整个字符串。在 idx=6
和 idx=7
时,我们会再次找到另一个片段并将其长度添加到结果列表。
result
的值将为 [3, 5]
,这意味着我们可以将字符串划分为 "aba" 和 "cccd" 这两个片段。
from typing import List
class Solution:
def partitionLabels(self, s: str) -> List[int]:
# 记录每个字符的最后出现位置
last_occurrence = {char: idx for idx, char in enumerate(s)}
start, end = 0, 0
result = []
for idx, char in enumerate(s):
# 更新当前片段的结束位置
end = max(end, last_occurrence[char])
# 当遍历到当前片段的结束位置,记录片段长度并开始新的片段
if idx == end:
result.append(end - start + 1)
start = idx + 1
return result
代码步骤:
last_occurrence = {char: idx for idx, char in enumerate(s)}
此字典为我们提供了每个字符的最后出现位置的快速查找。
初始化start
和end
为0
:
start
是当前片段的开始位置。end
是当前片段的预期结束位置。遍历字符串s
的每个字符和其索引:
for idx, char in enumerate(s):
end
值中的较大者来更新end
。end = max(end, last_occurrence[char])
idx
(当前遍历的位置)等于end
时,这意味着从start
到end
是一个有效的片段,因为我们确保了这个片段中的所有字符都不会在之后出现。if idx == end: result.append(end - start + 1) start = idx + 1
result
中。start
设置为idx + 1
,以开始新的片段。result
。力扣题目链接(opens new window)
给出一个区间的集合,请合并所有重叠的区间。
示例 1:
示例 2:
要解决这个问题,我们可以遵循以下步骤:
merged
来存储合并后的区间。interval
:
merged
为空或者 merged
中最后一个区间的结束位置小于当前 interval
的起始位置,说明当前区间与 merged
中的所有区间都不重叠,可以直接将 interval
添加到 merged
。merged
中最后一个区间有重叠,我们需要更新 merged
中最后一个区间的结束位置为当前区间的结束位置和 merged
中最后一个区间的结束位置的较大值。merged
中存储的就是合并后的区间列表。from typing import List
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
if not intervals:
return []
# Step 2: Sort the intervals based on the start of each interval
intervals.sort(key=lambda x: x[0])
merged = [intervals[0]]
# Step 3 & 4: Iterate through the intervals
for interval in intervals[1:]:
# If the current interval does not overlap with the previous, add it to merged
if merged[-1][1] < interval[0]:
merged.append(interval)
# If it does overlap, merge the current interval with the previous one
else:
merged[-1][1] = max(merged[-1][1], interval[1])
# Step 5: Return the merged intervals
return merged
让我们以示例 [[1,3],[2,6],[8,10],[15,18]]
来解释这段代码的逻辑。
首先,我们创建一个 Solution
类的实例,并调用 merge
方法。
sol = Solution() result = sol.merge([[1,3],[2,6],[8,10],[15,18]])
在 merge
方法中,首先检查输入的 intervals
是否为空。如果为空,直接返回空列表。否则,继续执行。
接下来,对 intervals
按照每个区间的起始位置进行排序。排序后的 intervals
将变为 [[1,3],[2,6],[8,10],[15,18]]
。
初始化一个列表 merged
并将排序后的第一个区间 [1,3]
加入其中。
现在,遍历剩余的区间:
[2,6]
,merged
中最后一个区间是 [1,3]
。因为 [1,3]
的结束位置 3
大于等于 [2,6]
的起始位置 2
,我们知道这两个区间是重叠的。所以我们将 merged
中最后一个区间的结束位置更新为 max(3,6)
,即 6
。现在,merged
变为 [[1,6]]
。[8,10]
,merged
中最后一个区间是 [1,6]
。因为 [1,6]
的结束位置 6
小于 [8,10]
的起始位置 8
,我们知道这两个区间不重叠。所以直接将 [8,10]
加入到 merged
中。现在,merged
变为 [[1,6],[8,10]]
。[15,18]
,同理,直接加入到 merged
中,因为它不与 [8,10]
重叠。现在,merged
变为 [[1,6],[8,10],[15,18]]
。遍历完成后,返回 merged
,即 [[1,6],[8,10],[15,18]]
。这就是合并后的区间列表。