Leetcode题解-算法-贪心算法(python版)

1、数对可以组成的最长链

646. 最长数对链(Medium)
方法一:贪心算法
对所有的数对按照第二个数的大小进行排序。第一步选结束数字最小的那个数对。 然后,每步都选和上一个选中的数对不冲突且结束数字最小的那个数。

class Solution:
    def findLongestChain(self, pairs: List[List[int]]) -> int:
        pairs.sort(key=lambda x: (x[1], x[0]))

        res = 1
        tail = pairs[0][1]
        for _, item in enumerate(pairs):
            if item[0] > tail:
                tail = item[1]
                res += 1
        return res

方法二:动态规划
对所有的数对按照第一个数的大小进行排序。子问题为数对 pairs[i](i=1, 2, 3…N)为终点的最长链。a[i] 表示已数对 pairs[i]为终点的链长度。

  • 初始状态:a[i] = 1
  • a[i] = max { a[i], a[j]+1} 0<= j< i 且 pairs[i][0]>pairs[j][1]

再寻找 a[i] 的最大值。

class Solution:
    def findLongestChain(self, pairs: List[List[int]]) -> int:
        pairs.sort()
        dp = [1] * len(pairs)
        for i in range(len(pairs)):
            for j in range(i):
                if pairs[i][0] > pairs[j][1]:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)

2、分配饼干

455. 分发饼干(Easy)
方法:排序 + 贪心
为了不浪费饼干,应该给每个孩子分配可以使该孩子满足,且大小最小的饼干。

class Solution:
    def findContentChildren(self, g: List[int], s: List[int]) -> int:
        g.sort()
        s.sort()
        i = j = res = 0
        len_g, len_s = len(g), len(s)
        while i < len_g and j < len_s:
            if s[j] >= g[i]:
                res += 1
                i += 1
            j += 1
        return res

3、不重叠的区间个数

435. 无重叠区间(Medium)
贪心思想,计算不重复的区间最多多少个,然后用区间总个数减去不重叠区间的个数。将区间排序按照结尾数字排序,这次选的结尾越小,留给后面的区间的空间越大。每次选结尾最小且与前面不重叠的那个区间。

class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort(key=lambda x:(x[1], x[0]))
        tail = intervals[0][1]
        res = 1
        for _, item in enumerate(intervals):
            if item[0] >= tail:
                res += 1
                tail = item[1]
        return len(intervals)-res

4、最少需要多少飞镖刺破气球

452. 用最少数量的箭引爆气球(Medium)
方法:排序 + 贪心

这里是计算不重叠的区间个数一共有多少个,按照每个区间的结束坐标排序,选取第一个区间,以后每次选取与当前区间不重叠且结束坐标最小的区间。

class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        points.sort(key=lambda x:(x[1], x[0]))
        tail = points[0][1]
        res = 1
        for _, item in enumerate(points):
            if item[0] > tail:
                tail = item[1]
                res += 1
        return res

5、根据身高序号重排序

406. 根据身高重建队列(Medium)
按照身高降序排序,身高相等的按序号升序排序,遍历所有的人,将每个人插到对应位置上。
当放入第 i 个人时:

  • 第 0,⋯,i−1 个人已经在队列中被安排了位置,他们只要站在第 i 个人的前面,就会对第 i 个人产生影响,因为他们都比第 i 个人高;
  • 而第 i+1,⋯,n−1 个人还没有被放入队列中,并且他们无论站在哪里,对第 i 个人都没有任何影响,因为他们都比第 i 个人矮。

后面的人既然不会对第 i 个人造成影响,可以采用插空的方法,当我们放入第 i 个人时,将其插入队列中,使得他的前面恰好有 ki 个人即可

举例说明:[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
排序:
[7, 0] [7, 1] [6, 1] [5, 0] [5, 2] [4, 4]

插入:
[7, 0]
[7, 0] [7, 1]
[7, 0] [6, 1] [7, 1]
[5, 0] [7, 0] [6, 1] [7, 1]
[5, 0] [7, 0] 5, 2] [6, 1] [7, 1]
[5, 0] [7, 0] 5, 2] [6, 1] [4, 4] [7, 1]

class Solution:
    def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
        people.sort(key=lambda x: (-x[0], x[1]))
        ans = list()
        for item in people:
            if len(ans) <= item[1]:
                ans.append(item)
            else:
                ans.insert(item[1], item)
        return ans

下面的写法也可以实现插入效果

class Solution:
    def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
        people.sort(key=lambda x: (-x[0], x[1]))
        ans = list()
        for item in people:
            ans[item[1]:item[1]] = [item]
        return ans

6、买卖股票最大的收益

121. 买卖股票的最佳时机(Easy)
记录前面的最小价格,将这个价格作为买入价格,当前价格作为卖出价格,当前利润大于前面的利润,更新利润。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        small_price = prices[0]
        res = 0
        for _, item in enumerate(prices):
            small_price = min(small_price, item)
            res = max(res, item-small_price)
        return res

7、买卖股票最大的收益Ⅱ

122. 买卖股票的最佳时机 II(Medium)
只要价格在增加,就有收益。
对于 [a, b, c, d],如果有 a <= b <= c <= d ,那么最大收益为 d - a。而 d - a = (d - c) + (c - b) + (b - a) ,因此当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0,那么就把 prices[i] - prices[i-1] 添加到收益中。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        res = 0
        n = len(prices)
        for i in range(1, n):
            if prices[i] > prices[i-1]:
                res += prices[i] - prices[i-1]
        return res

8、种植花朵

605. 种花问题(Easy)
首先将数组变成 [1, 0] + flowerbed + [0, 1],这样就不需要考虑收尾的问题了
下标 i,j 有花,之间是空地,则之间还能种 (j-i-2)//2 朵花。维护 pre_flower 表示上一朵已经种植的花的下标位置。从左往右遍历数组 flowerbed,当遇到 flowerbed[i]=1 时根据 pre_flower 和 i 的值计算上一个区间内可以种植花的最多数量,并更新pre_flower。最后判断整个花坛内可以种入的花的最多数量是否大于等于 n。

在循环中当可以种植的花朵已经大于等于n,可以直接跳出循环。

class Solution:
    def canPlaceFlowers(self, flowerbed: List[int], n: int) -> bool:
        flowerbed.insert(0, 0)
        flowerbed.insert(0, 1)
        flowerbed.append(0)
        flowerbed.append(1)
        pre_flower, res = 0, 0
        for i, item in enumerate(flowerbed):
            if i > 0 and item:
                res += (i-pre_flower-2)//2
                if res >= n:
                    return True
                pre_flower = i
        return res >= n

9、判断是否是字串

392. 判断子序列(Easy)
方法:双指针法
初始化两个下标 i 和 j,分别指向 s 和 t 的初始位置。匹配成功则同时右移,匹配失败则 j 右移,i 不变。

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        s_len = len(s)
        t_len = len(t)
        i, j = 0, 0
        while i < s_len and j < t_len:
            if s[i] == t[j]:
                i += 1
            j += 1
        return i >= s_len

10、修改一个数成为非递减数组

665. 非递减数列(Medium)
遍历数组,如果遇到递减:
还能修改:
修改方案1:将nums[i]缩小至nums[i + 1];
修改方案2:将nums[i + 1]放大至nums[i];
不能修改了:直接返回false;

class Solution:
    def checkPossibility(self, nums: List[int]) -> bool:
        n = len(nums)
        if n <=1: return True
        flag = True if nums[0] <= nums[1] else False
        for i in range(1, n-1):
            if nums[i] > nums[i+1]:
                if flag:
                    flag = False
                    if nums[i+1] >= nums[i-1]:
                        nums[i] = nums[i+1]
                    else:
                        nums[i+1] = nums[i]
                else:
                    return flag
        return True

11、连续子数组的最大和

53. 最大子数组和(Easy)

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        res, cur  = nums[0], 0
        n = len(nums)
        for _, item in enumerate(nums):
            cur += item
            res = max(res, cur)6785
            cur = cur if cur > 0 else 0
        return res

12、分隔字符串使同种字符出现在一起

763. 划分字母区间(Medium)
首先统计字符串中每种字符最后一次出现的位置。从前向后遍历字符串种每一个字符,查看这些字符最后一次出现的位置,当前位置等于前面这些字符最后一次出现的位置时,说明前面查看的这些字符在后面的字符中不会出现,可以在这里将字符串分隔开。

class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        last = [0] * 26
        for i, c in enumerate(s):
            last[ord(c) - ord('a')] = i

        begin, end = 0, 0
        res = []
        for i, c in enumerate(s):
            end = max(end, last[ord(c) - ord('a')])
            if end == i:
                res.append(i - begin + 1)
                begin = i + 1
        return res

13、最小差值 II

910. 最小差值 II(Medium)

我们把原数组先排一下序,然题目要求每个元素要么向上移动 K 的距离,要么向下移动 K 的距离,然后要求这个新数组的“最大最小值的距离尽可能地小”。将数组拆成两半,把左边那一半上移,把右边那一半下移,如下图所示。黑色是原数组,红色是新数组:
在这里插入图片描述
在 i 这一点分割数组,A[0] ~ A[i] 元素都上移,A[i + 1] ~ A[A.length - 1] 的元素都下移。
此时 B 点的值是 A[i] + K,D 点的值是 A[A.length - 1] - K。
新数组的最大值要么是 B 点要么是 D 点,也就是说新数组的最大值是 Max(A[i] + K, A[A.length - 1] - K)。

同样道理,此时 A 点的值是 A[0] + K,C 点的值是 A[i + 1] - K。
新数组的最小值要么是 A 点要么是 C 点,也就是说新数组的最小值是 Min(A[0] + K, A[i + 1] - K)。

题目需要的“新数组的最大值和最小值的差值”,就是 Max(A[i] + K, A[A.length - 1] - K) - Min(A[0] + K, A[i + 1] - K)。让 i 从 0 到 A.length - 2 挨个遍历,取上面算式的最小值即可。

class Solution(object):
    def smallestRangeII(self, A, K):
        len_A = len(A)
        A = sorted(A)
        res = A[len_A-1] - A[0]
        for i in range(0, len_A-1):
            max_num = max(A[i]+K, A[len_A-1]-K)
            min_num = min(A[0]+K, A[i+1]-K)
            res = min(res, max_num-min_num)
        return res

14、摆动序列

376. 摆动序列(Medium)

当序列中有一段连续的递增(或递减)时,为形成摇摆子序列,我们只需要保留这段连续的递增(或递减)的首位元素,这样更可能使得尾部的后一个元素成为摇摆子序列的下一个元素。
在这里插入图片描述

class Solution(object):
    def wiggleMaxLength(self, nums):
        len_num = len(nums)
        if len_num <= 1:
            return len_num
        pre_diff = nums[1] - nums[0]
        res = 1 if pre_diff == 0 else 2
        for i in range(2, len_num):
            diff = nums[i] - nums[i-1]
            if (diff < 0 and pre_diff >= 0) or (diff > 0 and pre_diff <= 0):
                res += 1
                pre_diff = diff
        return res 

时间复杂度: O(n)
空间复杂度: O(1)

15、发放糖果

135. 分发糖果(Hard)
思路:两次遍历
把所有孩子的糖果数初始化为 1;先从左往右遍历一遍,如果右边孩子的评分比左边的高,则右边孩子的糖果数更新为左边孩子的糖果数加 1;再从右往左遍历一遍,如果左边孩子的评分比右边的高,且左边孩子当前的糖果数不大于右边孩子的糖果数,则左边孩子的糖果数更新为右边孩子的糖果数加 1

class Solution:
    def candy(self, ratings: List[int]) -> int:
        n = len(ratings)
        num = [1] * n
        for i in range(n - 1):
            if ratings[i + 1] > ratings[i]:
                num[i + 1] = num[i] + 1
        for i in range(n - 1, 0, -1):
            if ratings[i - 1] > ratings[i] and num[i - 1] <= num[i]:
                num[i - 1] = num[i] + 1
        return sum(num)

时间复杂度: O(n)
空间复杂度: O(n)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值