代码随想录训练营 Day30打卡 贪心算法 part04 452. 用最少数量的箭引爆气球 435. 无重叠区间 763. 划分字母区间

代码随想录训练营 Day30打卡 贪心算法 part04

一、 力扣452. 用最少数量的箭引爆气球

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。
示例
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
-在x = 6处射出箭,击破气球[2,8]和[1,6]。
-在x = 11处发射箭,击破气球[10,16]和[7,12]。

局部最优:当气球出现重叠,一起射,所用弓箭最少。全局最优:把所有气球射爆所用弓箭最少。
为了让气球尽可能的重叠,需要对数组进行排序。
如果气球重叠了,重叠气球中右边边界的最小值 之前的区间一定需要一个弓箭。

以题目示例: [[10,16],[2,8],[1,6],[7,12]]为例,如图:(方便起见,已经排序)
在这里插入图片描述

可以看出首先第一组重叠气球,一定是需要一个箭,气球3 的左边界大于了 第一组重叠气球的最小右边界,所以再需要一支箭来射气球3了。

版本一

初始化箭的数量为1,因为至少需要一支箭来射穿第一个气球。
从第二个气球开始遍历,检查每个气球的起始点与前一个气球的终点。
如果当前气球的起点大于前一个气球的终点,说明这两个气球不重叠,需要增加一支箭。
如果重叠,则更新当前气球的右边界为两者的较小值,以便下一次判断是否可以用同一支箭射穿。

class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        # 如果没有气球,返回0
        if len(points) == 0:
            return 0
        
        # 将气球按起始点升序排序
        points.sort(key=lambda x: x[0])
        
        # 初始化箭的数量为1,默认射出第一箭
        result = 1
        
        # 遍历排序后的气球列表,从第二个气球开始
        for i in range(1, len(points)):
            # 如果当前气球的起点大于前一个气球的终点,说明这两个气球不重叠,需要一支新的箭
            if points[i][0] > points[i - 1][1]:  # 注意这里用的是 '>' 而不是 '>='
                result += 1  # 增加箭的数量
            else:
                # 如果气球重叠,更新当前的最小右边界为两者中的较小值
                # 这样保证在重叠的气球中,一箭可以射穿所有重叠的气球
                points[i][1] = min(points[i - 1][1], points[i][1])
        
        # 返回总共需要的箭的数量
        return result

版本二

初始化最小左边界sl和最小右边界sr为第一个气球的位置。
初始化箭的数量为1,因为至少需要一支箭来射穿第一个气球。
遍历所有气球,对于每个气球,检查其起点与当前记录的最小右边界sr。
如果当前气球的起点大于sr,说明这些气球不重叠,需要一支新的箭,并更新最小左边界和最小右边界为当前气球的位置。
如果当前气球和之前的气球重叠,则更新最小左边界和最小右边界,以确保能用同一支箭射穿重叠的气球。

class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        # 将气球按起始点升序排序
        points.sort(key=lambda x: x[0])
        
        # 初始化最小左边界和最小右边界为第一个气球的位置
        sl, sr = points[0][0], points[0][1]
        
        # 初始化箭的数量为1
        count = 1
        
        # 遍历排序后的气球列表
        for i in points:
            # 如果当前气球的起点大于当前记录的最小右边界,说明需要一支新的箭
            if i[0] > sr:
                count += 1  # 增加箭的数量
                sl, sr = i[0], i[1]  # 更新最小左边界和最小右边界为当前气球的位置
            else:
                # 如果当前气球和之前的气球重叠,更新最小左边界和最小右边界
                sl = max(sl, i[0])  # 更新最小左边界为较大的起点
                sr = min(sr, i[1])  # 更新最小右边界为较小的终点
        
        # 返回总共需要的箭的数量
        return count

力扣题目链接
题目文章讲解
题目视频讲解

二、 力扣435. 无重叠区间

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的 最小数量 ,使剩余区间互不重叠 。
示例
输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。

版本一

  1. 检查输入:
    如果intervals为空,直接返回0,因为没有区间需要处理。
  2. 排序:
    将区间按照左边界x[0]进行升序排序。这样可以保证后续的区间从左到右处理,方便判断是否重叠。
  3. 遍历区间:
    从第二个区间开始,检查当前区间的左边界是否小于前一个区间的右边界。
    如果重叠,更新当前区间的右边界为两个区间右边界的较小值,以减少后续的重叠可能性,并增加重叠计数count。
class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        if not intervals:
            return 0  # 如果区间列表为空,返回0

        intervals.sort(key=lambda x: x[0])  # 按照左边界升序排序
        
        count = 0  # 记录重叠区间的数量
        
        # 从第二个区间开始遍历
        for i in range(1, len(intervals)):
            if intervals[i][0] < intervals[i - 1][1]:  # 当前区间的左边界小于前一个区间的右边界,说明重叠
                # 如果重叠,更新当前区间的右边界为较小值,以减少后续的重叠可能性
                intervals[i][1] = min(intervals[i - 1][1], intervals[i][1])
                count += 1  # 记录一次重叠
        
        return count  # 返回需要移除的重叠区间的数量

版本二

  1. 检查输入:
    如果intervals为空,直接返回0,因为没有区间需要处理。
  2. 排序:
    将区间按照左边界x[0]进行升序排序,确保从左到右逐个处理区间。
  3. 初始化不重叠区间计数:
    result初始值设为1,因为至少会有一个不重叠的区间(第一个区间)。
  4. 遍历区间:
    从第二个区间开始,检查当前区间的左边界是否大于等于前一个区间的右边界。
    如果不重叠,增加不重叠区间的计数result。
    如果重叠,更新当前区间的右边界为两个区间右边界的较小值,以减少后续的重叠可能性。
class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        if not intervals:
            return 0  # 如果区间列表为空,返回0
        
        intervals.sort(key=lambda x: x[0])  # 按照左边界升序排序
        
        result = 1  # 记录不重叠区间的数量,初始化为1,因为至少有一个不重叠的区间
        
        # 从第二个区间开始遍历
        for i in range(1, len(intervals)):
            if intervals[i][0] >= intervals[i - 1][1]:  # 当前区间的左边界大于等于前一个区间的右边界,说明不重叠
                result += 1  # 不重叠的区间数加1
            else:  # 如果重叠
                # 更新当前区间的右边界为较小值,以减少后续的重叠可能性
                intervals[i][1] = min(intervals[i - 1][1], intervals[i][1])
        
        # 总区间数减去不重叠的区间数,就是需要移除的区间数
        return len(intervals) - result

力扣题目链接
题目文章讲解
题目视频讲解

三、 力扣763. 划分字母区间

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。
返回一个表示每个字符串片段的长度的列表。
示例
输入:s = “ababcbacadefegdehijhklij”
输出:[9,7,8]
解释
划分结果为 “ababcbaca”、“defegde”、“hijhklij” 。
每个字母最多出现在一个片段中。
像 “ababcbacadefegde”, “hijhklij” 这样的划分是错误的,因为划分的片段数较少。

在遍历的过程中相当于是要找每一个字母的边界,**如果找到之前遍历过的所有字母的最远边界,说明这个边界就是分割点了。**此时前面出现过所有字母,最远也就到这个边界了。

可以分为如下两步:

  • 统计每一个字符最后出现的位置
  • 从头遍历字符,并更新字符的最远出现下标,如果找到字符最远出现位置下标和当前下标相等了,则找到了分割点

如图:
在这里插入图片描述

版本一

  1. 记录字符的最后出现位置:

使用字典last_occurrence记录每个字符在字符串中最后出现的位置。通过遍历字符串,将每个字符的最新位置存入字典。

  1. 初始化变量:

result用于存储最终划分出的每个区间的长度。
start和end分别表示当前区间的起始和结束位置。

  1. 遍历字符串进行区间划分:

在遍历字符串时,不断更新当前区间的结束位置end为当前字符的最后出现位置。
当当前位置i等于end时,意味着当前区间可以结束,将这个区间的长度添加到结果中,并更新起始位置start。

class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        last_occurrence = {}  # 存储每个字符最后出现的位置
        for i, ch in enumerate(s):
            last_occurrence[ch] = i  # 记录字符ch在字符串s中的最后出现位置

        result = []  # 存储每个区间的长度
        start = 0  # 当前区间的起始位置
        end = 0  # 当前区间的结束位置
        
        for i, ch in enumerate(s):
            # 更新当前区间的结束位置为字符ch最后出现的位置
            end = max(end, last_occurrence[ch])  
            
            # 如果当前位置i是当前区间的结束位置,则完成一个区间的划分
            if i == end:
                result.append(end - start + 1)  # 将区间的长度加入结果列表
                start = i + 1  # 更新起始位置为下一个字符的位置
        
        return result  # 返回划分出的区间长度列表

版本二

  1. 统计每个字符的起始和结束位置:

countLabels函数通过遍历字符串,记录每个字母的起始和结束位置。使用长度为26的数组hash存储这些位置,hash[i][0]表示字母i的起始位置,hash[i][1]表示字母i的结束位置。

  1. 过滤有效的区间:

遍历hash数组,过滤掉未出现的字母,只保留有效的区间信息,存入hash_filter列表。

  1. 排序区间:

按照区间的左边界从小到大排序,为后续区间合并和划分做准备。

  1. 遍历区间进行划分:

初始化rightBoard为第一个区间的右边界,leftBoard为0。
遍历排序后的区间列表,若发现当前区间的左边界大于当前的最大右边界,说明可以划分出一个新的区间。更新左边界和右边界,并记录划分出的区间长度。

  1. 添加最后一个区间:

在循环结束后,别忘了添加最后一个区间的长度。

class Solution:
    def countLabels(self, s):
        # 初始化一个长度为26的区间列表,用于记录每个字母的起始和结束位置
        hash = [[float('-inf'), float('-inf')] for _ in range(26)]
        hash_filter = []
        
        # 遍历字符串,记录每个字母的起始和结束位置
        for i in range(len(s)):
            # 如果当前字母第一次出现,记录它的起始位置
            if hash[ord(s[i]) - ord('a')][0] == float('-inf'):
                hash[ord(s[i]) - ord('a')][0] = i
            # 更新当前字母的结束位置为最新位置
            hash[ord(s[i]) - ord('a')][1] = i
        
        # 过滤掉没有出现的字母,生成有效的区间列表
        for i in range(len(hash)):
            if hash[i][0] != float('-inf'):
                hash_filter.append(hash[i])
        
        return hash_filter  # 返回有效的区间列表

    def partitionLabels(self, s):
        res = []
        hash = self.countLabels(s)  # 获取有效的区间列表
        hash.sort(key=lambda x: x[0])  # 按左边界从小到大排序
        
        rightBoard = hash[0][1]  # 初始化最大右边界为第一个区间的右边界
        leftBoard = 0  # 初始化左边界为0
        
        for i in range(1, len(hash)):
            # 如果当前区间的左边界大于当前的最大右边界,说明可以划分一个新的区间
            if hash[i][0] > rightBoard:
                res.append(rightBoard - leftBoard + 1)  # 添加划分的区间长度到结果列表
                leftBoard = hash[i][0]  # 更新左边界为当前区间的左边界
            
            # 更新当前的最大右边界
            rightBoard = max(rightBoard, hash[i][1])
        
        # 添加最后一个区间的长度到结果列表
        res.append(rightBoard - leftBoard + 1)
        
        return res  # 返回划分出的区间长度列表

力扣题目链接
题目文章讲解
题目视频讲解

  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值