Day22: 贪心算法 | 区间问题,左/右端点排序

1.452用最少数量的箭引爆气球

只按右端点排序(左端点大小无所谓),射在右端点,尽可能多射到气球

class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        points.sort(key = lambda x :x[1])
        arrows = 1
        position = points[0][1]

        for point in points[1:]:
            if point[0] > position:
                arrows += 1
                position = point[1]
        return arrows 

2.435. 无重叠区间

和上一题一样,这俩题本质是一道题,也是只按右端点排序,重叠则删除右端点大的区间,这样可以尽可能避免重叠。

class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort(key = lambda x :x[1])
        n = len(intervals)
        remove = 0
        curr_end = intervals[0][1]
        for i in range(1,n):
            if intervals[i][0] < curr_end:
                remove += 1
            else:
                curr_end = intervals[i][1]
        return remove

3.763. 划分字母区间

缝缝补补才写出来,我的方法太过于复杂

构建字典 → 提取区间 → 排序 → 合并区间

class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        res = []
        
        char_intervals = defaultdict(list)
        for i,ch in enumerate(s):
            if ch not in char_intervals:
                char_intervals[ch] = [i,i]
            char_intervals[ch][1] = i
        
        intervals = list(char_intervals.values())
        intervals.sort(key = lambda x: x[0])
        start = 0
        curr_end = intervals[0][1]
        for interval in intervals:
            if interval[0] > curr_end:
                res.append(curr_end-start+1 )
                curr_end = interval[1]
                start = interval[0]     
            else:
                curr_end = max(curr_end, interval[1])       

        res.append(curr_end-start+1) 
        
        return res

优化:

只记录字符最后一次出现的位置,遍历字符,扩展end边界。

class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        last_index = {ch: i for i, ch in enumerate(s)}
        
        res = []
        start = end = 0
        
        for i, ch in enumerate(s):
            end = max(end, last_index[ch])
            
            if i == end:
                res.append(end - start + 1)
                start = i + 1
        
        return res

4.56. 合并区间

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        intervals.sort(key = lambda x:x[0])
        res = [intervals[0]]
        for interval in intervals[1:]:
            if interval[0] > res[-1][1]:
                res.append(interval)
            else:
                start,end = res.pop()
                res.append([start, max(interval[1],end)])
            
        return res

优化:else部分,可以直接修改,不用pop

res[-1][1] = max(res[-1][1],interval[1])

6.738. 单调递增的数字

我想的是从左往右扫描,需要回溯去找mark的位置,逻辑比较复杂

  1. str不能直接修改,转换为list修改
  2. 字符比较大小时通过ASCII码比较,这里是单个字符,可以直接比较大小 (‘1’与‘5’)
  3. 处理重复数字用while,这里回头往前探测
  4. 找到mark,将数字减一,注意字符与数字的转换
  5. 后面的字符全改为‘9’
  6. 字符拼接并转为int
    1. int可以直接处理0开头的字符串,‘019’-->19
class Solution:
    def monotoneIncreasingDigits(self, n: int) -> int:
        #找到第一个下降的数字
        #找到它前面的数字(可能有重复)-1,后面全为9
        #字符与数字的转换
        digits = list(str(n)) #['1','5','0']
        n = len(digits)
        mark = None
        for i in range(n):
            if i > 0 and digits[i] < digits[i-1]:
                mark = i-1
                while mark > 0 and digits[mark-1] == digits[mark]:
                    mark -= 1
                break
        if mark is not None:
            digits[mark] = str(int(digits[mark]) - 1)
            for i in range(mark+1, n):
                digits[i] = '9'
        
        return int(''.join(digits))
        

优化:从右往左扫描,不用回溯逻辑清晰多了。老是忘记初始化mark。。

class Solution:
    def monotoneIncreasingDigits(self, n: int) -> int:
        digits = list(str(n))
        n = len(digits)
        mark = n #随便一个不存在的位置
        for i in range(n-1, 0,-1):#[n-1,1]
            if digits[i-1] > digits[i]:
                digits[i-1] = str(int(digits[i-1]) -1)
                mark = i-1
        for i in range(mark+1, n):
            digits[i] = '9'
        
        return int(''.join(digits))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值