秋招算法备战第36天 | 435. 无重叠区间、763.划分字母区间、56. 合并区间

435. 无重叠区间 - 力扣(LeetCode)

这个问题可以通过贪心算法来解决。首先,我们将区间按照结束时间进行升序排序,然后我们可以从左到右遍历区间,选择结束时间最早的区间,并从集合中移除所有与该区间重叠的其它区间。重复此过程,直到集合为空。

以下是一个可能的实现:

def eraseOverlapIntervals(intervals):
    if not intervals:
        return 0

    # 按照结束时间进行升序排序
    intervals.sort(key=lambda x: x[1])

    # 初始化计数器和第一个区间的结束时间
    count = 0
    end_time = intervals[0][1]

    # 从第二个区间开始遍历
    for i in range(1, len(intervals)):
        # 如果区间的开始时间小于上一个区间的结束时间,说明有重叠
        if intervals[i][0] < end_time:
            count += 1
        else:
            # 如果没有重叠,更新结束时间
            end_time = intervals[i][1]

    return count

intervals1 = [[1,2],[2,3],[3,4],[1,3]]
intervals2 = [[1,2],[1,2],[1,2]]
intervals3 = [[1,2],[2,3]]
print(eraseOverlapIntervals(intervals1))  # 输出: 1
print(eraseOverlapIntervals(intervals2))  # 输出: 2
print(eraseOverlapIntervals(intervals3))  # 输出: 0

这个实现的时间复杂度为O(n log n),其中n为区间的数量。排序占用了主要的时间复杂度。空间复杂度为O(1),只使用了常数额外空间。

763. 划分字母区间 - 力扣(LeetCode)

这个问题可以通过贪心算法来解决。我们可以先遍历一遍字符串,找出每个字母的最后一次出现的位置。然后再次遍历字符串,根据每个字母的最后一次出现的位置,来确定每个片段的范围。

以下是一个可能的实现:

def partitionLabels(s):
    # 记录每个字母最后一次出现的位置
    last_occurrence = {char: index for index, char in enumerate(s)}
    
    result = []
    start = 0
    end = 0
    
    # 再次遍历字符串
    for index, char in enumerate(s):
        # 更新片段的结束位置
        end = max(end, last_occurrence[char])
        
        # 如果达到片段的结束位置,则记录片段长度,并更新下一个片段的开始位置
        if index == end:
            result.append(end - start + 1)
            start = index + 1
            
    return result

s1 = "ababcbacadefegdehijhklij"
s2 = "eccbbbbdec"
print(partitionLabels(s1))  # 输出: [9,7,8]
print(partitionLabels(s2))  # 输出: [10]

这个实现的时间复杂度为O(n),其中n为字符串的长度。我们只需要两次遍历字符串。空间复杂度也为O(n),用于存储每个字母的最后一次出现的位置。

56. 合并区间 - 力扣(LeetCode)

合并重叠区间的问题可以通过先按照每个区间的开始时间排序,然后遍历区间来解决。

以下是一个可能的实现:

def merge(intervals):
    # 如果区间为空或只有一个区间,直接返回
    if len(intervals) <= 1:
        return intervals
    
    # 按照开始时间进行升序排序
    intervals.sort(key=lambda x: x[0])
    
    result = []
    current_interval = intervals[0]
    
    # 遍历排序后的区间
    for i in range(1, len(intervals)):
        # 如果下一个区间的开始时间小于或等于当前区间的结束时间,说明有重叠,合并它们
        if intervals[i][0] <= current_interval[1]:
            current_interval[1] = max(current_interval[1], intervals[i][1])
        else:
            # 如果没有重叠,添加当前区间到结果,并更新当前区间
            result.append(current_interval)
            current_interval = intervals[i]
            
    # 将最后一个区间添加到结果
    result.append(current_interval)
    
    return result

intervals1 = [[1,3],[2,6],[8,10],[15,18]]
intervals2 = [[1,4],[4,5]]
print(merge(intervals1))  # 输出: [[1,6],[8,10],[15,18]]
print(merge(intervals2))  # 输出: [[1,5]]

这个实现的时间复杂度为O(n log n),其中n为区间的数量。排序占用了主要的时间复杂度。空间复杂度为O(1),只使用了常数额外空间。

总结(详细版)

1. 无重叠区间(435)

问题描述:给定一个区间集合,返回需要移除区间的最小数量,使剩余区间互不重叠。

解决方案:通过贪心算法实现,按结束时间排序,然后遍历区间,选择结束时间最早的区间,移除所有与该区间重叠的其他区间。

时间复杂度:O(n log n),排序占主要时间。
空间复杂度:O(1)。

2. 划分字母区间(763)

问题描述:给定一个字符串,将其划分为尽可能多的片段,使同一字母最多出现在一个片段中。

解决方案:先遍历一遍字符串,找出每个字母的最后一次出现的位置。然后再次遍历,根据每个字母的最后一次出现的位置来确定每个片段的范围。

时间复杂度:O(n),遍历字符串两次。
空间复杂度:O(n),存储每个字母的最后一次出现位置。

3. 合并区间(56)

问题描述:给定若干个区间的集合,合并所有重叠的区间,返回一个不重叠的区间数组。

解决方案:按照每个区间的开始时间排序,然后遍历区间,合并重叠的区间。

时间复杂度:O(n log n),排序占主要时间。
空间复杂度:O(1)。

总结

这三个问题虽然都涉及到区间操作,但其解决方案分别使用了贪心、遍历和排序的组合。它们展示了如何通过对问题进行分析,选择合适的算法设计技术(例如贪心策略或排序)来高效解决问题。通过合理的策略选择,我们可以在不同的场景下达到最优或近似最优的解决方案。

总结(简洁版)

下面是对上述三个问题最重点、最核心部分的对比总结:

1. 无重叠区间(435)

核心:贪心选择结束时间最早的区间,并移除所有与之重叠的区间。

2. 划分字母区间(763)

核心:记录每个字母的最后出现位置,以此确定不重叠的片段范围。

3. 合并区间(56)

核心:排序并遍历区间,合并所有重叠的区间。

对比总结

  • 贪心 vs 遍历 vs 排序

    • 无重叠区间问题通过贪心选择获得最优解。
    • 划分字母区间问题通过两次遍历确定划分。
    • 合并区间问题通过排序和遍历完成合并。
  • 处理对象的差异

    • 无重叠区间和合并区间问题都涉及到区间的操作,但一个是移除重叠,一个是合并重叠。
    • 划分字母区间则处理的是字符串,与其他两题的区间操作有所不同。
  • 解决策略的差异

    • 无重叠区间问题强调的是选择,通过选择特定区间达到解决问题的目的。
    • 划分字母区间问题强调的是统计和划分,通过分析字符出现的位置实现划分。
    • 合并区间问题强调的是排序和合并,通过排序来简化合并过程。

这三个问题虽然都涉及到了区间或者划分,但其解决的核心机制存在明显差异,反映了算法设计中的多样性和灵活性。不同的问题结构和要求导致了解决方案上的重点和核心部分有所不同。

你可能感兴趣的:(算法)