[Python-贪心算法]

135. 分发糖果

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

你需要按照以下要求,给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。

  • 相邻两个孩子评分更高的孩子会获得更多的糖果。

请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目

解题思路

题目的要求是相邻两个孩子中评分更高的孩子会获得更多的糖果,这也就是说我们需要双边比较,有点像前面的求峰值点数。但是这道题让我们统计的是糖果数,双边比较的话太容易出错了。我们可以用两次单边比较求出。

第一次遍历,从左向右,这时候判断右边孩子是否大于左边孩子,如果大于,则在左边孩子获得糖果数量的基础上加1;如果不大于,则赋值为1即可。这里的初始化为赋值列表全为1即可。

第二次遍历,从右向左,这时候就要判断左孩子是否大于右孩子,如果大于,则在第一次遍历右孩子糖果数量的基础上加1,并与第一次遍历得到的左孩子糖果数量比较,取较大的那一个,因为较大的数量能同时满足两次遍历的结果;如果不大于,不作修改即可。

代码如下:

class Solution:
    def candy(self, ratings: List[int]) -> int:
        candy_ = [1] * len(ratings)
        for i in range(1,len(ratings)):
            #从左向右遍历
            if ratings[i] > ratings[i - 1]:
                candy_[i] = candy_[i - 1] + 1
            else :
                candy_[i] = 1
        print(candy_)
        for i in range(2,len(ratings) + 1):
            #从右向左遍历
            if ratings[len(ratings) - i] > ratings[len(ratings) - i + 1]:
                candy_[len(ratings) - i] = max(candy_[len(ratings) - i],candy_[len(ratings) - i + 1] + 1)
        print(candy_)
        return sum(candy_)

860. 柠檬水找零

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false

解题思路

注意,这道题不能按照自己有多少钱来算,因为这样算的话会把10美元拆开,这样就会出错。正确的算法应该是对手里的5美元和10美元分别计数,按照其数目多少判断是否能够找零。

分情况讨论。

1.如果客户给了5美元,那么5美元的数值加1.

2.如果客户给了10美元,那么5美元的数值减1,10美元的数值加1。

3.如果客户给了20美元,那么优先花出10美元,再花出5美元。

因此,这里的贪心就是花出去尽可能多的10美元,保留5美元,因为5美元在找零中是万能的。

在以上三种情况中还存在着不同的情境,在第二种情况下,如果5美元的数量不够,那么返回False;在第三种情况下,如果10美元或5美元的数量不够,那么返回False。代码如下:

class Solution:
    def lemonadeChange(self, bills: List[int]) -> bool:
        five = 0
        ten = 0
        for i in range(len(bills)):
            if bills[i] == 5:
                five += 1
            elif bills[i] == 10:
                ten += 1
                if five >= 1:
                    five -= 1
                else:
                    return False
            elif bills[i] == 20:
                if ten >= 1 and five >= 1:
                    ten -= 1
                    five -= 1
                elif ten >= 1 and five <= 0:
                    return False
                elif ten <= 0 and five >= 3:
                    five -= 3
                elif ten <=0 and five < 3:
                    return False
        return True

406. 根据身高重建队列

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

解题思路

这道题有点像前面的糖果题,都是从二维的角度来出题的,遇到这种类型的题,我们直接单边考虑即可。

首先我们先试试按照次序k排个序,对于[[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]],得到[[5, 0], [7, 0], [6, 1], [7, 1], [5, 2], [4, 4]],这样看数组还是很乱,身高和次序都没有排好。

我们再来尝试下按身高排序,得到[[7, 0], [7, 1], [6, 1], [5, 0], [5, 2], [4, 4]],这样的话就比较清楚些了,最起码排好了大体的身高次序,接下来只需要按照次序k插入元素即可。代码如下:

class Solution:
    def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
        people.sort(key = lambda x: (-x[0], x[1])) #按照身高从大到小,序号从小到大排
        que = []
        for p in people:
            que.insert(p[1],p)
        return que

注意,这里insert()函数的用法是,第一项p[1]代表插入的位置,这里的p[1]即为people数组中的次序k,例如[5,0]元素,就是插入到了数组中的位置0.第二项p代表需要插入的元素。

452. 用最少数量的箭引爆气球

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstartxend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 x``startx``end, 且满足 xstart ≤ x ≤ x``end,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points返回引爆所有气球所必须射出的 最小 弓箭数

解题思路

首先来画个图直观的看一下。这里的排序是为了能让最有可能重叠的球都尽量挨在一起,便于判断。

[Python-贪心算法]_第1张图片

可以发现,当球的右边界大于等于下一个球的左边界时,这两个球可以被一支箭射爆,当球的右边界小于下一个球的左边界时,这个球只能单独被射爆。

再来看可以重叠射爆的情况,两个球可以重叠时,我们怎么判断下个球能不能被重叠进去呢?观察一下就可以知道,重叠的区域是有限的,我们需要更新重叠的右边界,如上图绿线。更新之后,将新边界作为上一个球的右边界,与现在的球的左边界比较即可判断。代码如下:

class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        points.sort(key = lambda x: (x[0], x[1]))
        result = 0
        for i in range(1, len(points)):
            if points[i - 1][1] < points[i][0]:
                result += 1
            else :
                points[i][1] = min(points[i - 1][1], points[i][1])
        result += 1 #此处加一是因为循环中没法对最后一个球进行处理
        return result

435. 无重叠区间

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠

解题思路

这道题和上道题差不多,可以按照上道题的思路进行重叠的计数,直接输出即可。代码如下:

class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        result = 0 #用于标记重叠的个数
        intervals.sort(key = lambda x:(x[0], x[1]))
        for i in range(1, len(intervals)):
            if intervals[i][0] >= intervals[i - 1][1]: #左边界大于右边界不重叠
                continue
            else: #重叠
                result += 1
                intervals[i][1] = min(intervals[i - 1][1],intervals[i][1])
        return result
​

763. 划分字母区间

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s

返回一个表示每个字符串片段的长度的列表。

解题思路

贪心算法的题一般看第一眼都没啥思路,所以这时候画个图,把所有必要信息列在图中可能就有思路了,如下:

[Python-贪心算法]_第2张图片

这样思路就清晰多了,对于每个字符,我们统计出他的最远位置,然后与下标进行比较,如果下标与一段的最远位置相同了,就说明这是一段。代码如下:

class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        dic = {} #哈希表统计每个字符出现的最远位置
        for i in range(len(s)):
            dic[s[i]] = i
​
        ans = [] #存储答案
        index_max = 0 #用于表示每一段的最大下标
        len_max = 0 #用于表示每一段的最大长度
        for i in range(len(s)):
            index_max = max(dic.get(s[i], 0), index_max)
            len_max += 1
            if i == index_max:
                ans.append(len_max)
                len_max = 0
        return ans

56. 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间

解题思路

同样画图来看。

[Python-贪心算法]_第3张图片

根据图来模拟一下过程,与前面判断重叠的方法类似,都是看下一个区间的左边界与上个区间的右边界的关系来判断,只不过这里如果判为了重叠区间,则不是在对区间的右边界取最小值了,而是取最大值,因为这并不需要多个区间都有相同的一段。只需要连接在一起即可。

因为end随重叠区间改变,所以在判断为不是重叠区间时才将不重叠区间加入到答案数组中。代码如下:

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        intervals.sort(key = lambda x:(x[0], x[1]))
        ans = []
        start = intervals[0][0] #区间数组左边界
        end = intervals[0][1] #区间数组右边界
        for i in range(1, len(intervals)):
            #重叠
            if intervals[i - 1][1] >= intervals[i][0]:
                end = max(intervals[i][1], intervals[i - 1][1])
                intervals[i][1] = max(intervals[i][1], intervals[i - 1][1])
            #不重叠
            else:
                ans.append([start, end])
                start = intervals[i][0] #不重叠,更新区间数组
                end = intervals[i][1]
        ans.append([start, end])
        return ans

738. 单调递增的数字

当且仅当每个相邻位数上的数字 xy 满足 x <= y 时,我们称这个整数是单调递增的。

给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增

解题思路

这个思路比较好想,但是代码有点难写。具体思路就是将数字从尾到头遍历,如果靠后的数字小于靠前的数字,那么靠后的数字就要变为9,靠前的数字就要减一,这样才满足单调递增的要求。代码如下:

class Solution:
    def monotoneIncreasingDigits(self, n: int) -> int:
        n_s = str(n)
        index = len(n_s) #用于标记需要换为9的位置
        #不是单调递增
        for i in range(1, len(n_s)):
            if n_s[len(n_s) - i] < n_s[len(n_s) - i - 1]:
                index = len(n_s) - i
                n_s = n_s[:len(n_s) - i - 1] + str(int(n_s[len(n_s) - i - 1]) - 1) + n_s[len(n_s) - i:]
        for i in range(index, len(n_s)):
            n_s = n_s[:i] + '9' + n_s[i + 1:] 
        ans = int(n_s)
        return ans

关于字符串的操作还是不太熟练,还要继续练习。

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