贪心算法

  贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。

  贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关

钞票支付问题

设有无穷张面值为1元、5元、10元、20元、100元、200元的钞票,现要使用这些钞票支付 x x x 元,最少需要几张钞票。

思路:
贪心策略:观察现有钞票的面额发现,任意面额是比自己小的面额的倍数关系,所以当使用一张较大面额钞票时,若用较小面额钞票替换,一定需要更多的其他面额钞票,因此贪心策略就是首先要使用尽可能多的面额大的钞票。

def change(money,x):
    n=len(money)
    money.sort(reverse=True)
    ans=0
    for i in range(n):
        use=x//money[i]
        ans+=use
        x-=money[i]*use
        print('需要面额为{}的钞票{}张'.format(money[i],use))
    return ans
print(change([200,100,20,10,5,1],628))

455.分发饼干【简单】

LeetCode传送门
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
注意:你可以假设胃口值为正。一个小朋友最多只能拥有一块饼干。

思路: 贪心+双指针

  • 某个饼干如果不能满足某个孩子,则该饼干也一定不能满足需求因子更大的孩子;
  • 某个孩子可以用更小的饼干满足,则没必要用更大的饼干满足,因为可以保留更大的饼干满足需求因子更大的孩子;
  • 孩子的需求因子更小则其更容易被满足,故优先从需求因子小的孩子尝试,可以得到正确的结果。

贪心策略:对需求因子数组 g 和饼干大小数组 s 进行从小到大的排序,按照从小到大的顺序使用个饼干尝试是否可以满足某个孩子,每个饼干只尝试一次,若尝试成功,则换下一个孩子尝试,直到发现没更多孩子或者没更多的饼干,循环结束。

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

376.摆动序列【中等】

LeetCode传送门
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

思路: 贪心+有限状态机
当序列有一段连续的递增(或递减)时,为形成摇摆子序列,只需保留这段连续的递增(或递减)的首尾元素,这样更可能使得尾部的后一个元素成为摇摆子序列的下一个元素。
在这里插入图片描述

class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        if len(nums)<2:
            return len(nums)
        BEGIN,UP,DOWN=0,1,2
        STATE=BEGIN
        max_length=1
        for i in range(1,len(nums)):
            if STATE==BEGIN:
                if nums[i]>nums[i-1]:
                    STATE=UP
                    max_length+=1
                elif nums[i]<nums[i-1]:
                    STATE=DOWN
                    max_length+=1
            elif STATE==UP:
                if nums[i]<nums[i-1]:
                    STATE=DOWN
                    max_length+=1
            elif STATE==DOWN:
                if nums[i]>nums[i-1]:
                    STATE=UP
                    max_length+=1
        return max_length

402.移除 k 个数字【中等】

LeetCode传送门
给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。

注意:

  • num 的长度小于 10002 且 ≥ k。
  • num 不会包含任何前导零。

思路: 贪心+栈
若去掉某一位数字,为了使得到的新数字最小,需要尽可能让得到的新数字优先最高位最小,其次次高位最小,……

  • 暴力:从高位向低位遍历,如果对应的数字大于下一位数字,则把该位数字去掉,得到的数字最小。去掉 k 个数字,则从最高位遍历 k 次。
  • 使用栈存储最终结果或删除工作:从高位向低位遍历 num,
    • 如果栈为空或遍历的数字大于栈顶元素,则将该数字push入栈,需要注意,如果栈为空且数字为0(如100200),则不入栈;
    • 如果栈不空,且当前遍历元素小于栈顶元素则进行pop弹栈,直到栈为空或不能再删除数字(已删除 k 位)或栈顶元素小于当前元素为止。
    • 如果所有数字都扫描完成后,k 仍然大于0,则进行pop弹栈,直到删除 k 个元素;
    • 最后结果即从栈底遍历至栈顶。
class Solution:
    def removeKdigits(self, num: str, k: int) -> str:
        stack=[]
        for i in range(len(num)):
            number=int(num[i])
            while stack and number<stack[-1] and k>0:
                stack.pop()
                k-=1
            if stack or number!=0:[添加链接描述](https://leetcode-cn.com/problems/jump-game/)
                stack.append(number)
        while stack and k>0:
            stack.pop()
            k-=1
        
        result=''.join([str(c) for c in stack])
        if not result:
            result="0"
        return result

55.跳跃游戏【中等】

LeetCode传送门
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。

思路:
贪心思想:若从位置0最远可以跳至第 i 个位置,则从位置0也可以跳至位置1、位置2、……、位置i-1,假设从这些位置又可以向前跳至的位置分别为 index[1]、index[2]、……、index[i-1],则从位置0,应该跳至index[1]、index[2]、……、index[i-1]中最大的那个。

  • 计算从位置 i 最远可跳至的位置: i n d e x [ i ] = n u m s [ i ] + i index[i]=nums[i]+i index[i]=nums[i]+i
  • 设置变量 jump 代表当前所处的位置,初始化为0;
  • 设置变量 max_index 代表从位置0至位置jump这个过程中,最远可到达的位置,初始化为index[0];
  • 利用jump扫描index数组,直到jump达到index数组尾部或jump超过max_index,扫描过程中,更新max_index;
  • 若最终jump为数组长度,则返回true,否则返回false。
class Solution:
    def canJump(self, nums: List[int]) -> bool:
        index=[nums[i]+i for i in range(len(nums))]
        jump=0
        max_index=nums[0]

        while jump<len(index) and jump<=max_index:
            if max_index<index[jump]:
                max_index=index[jump]
            jump+=1

        if jump==len(index):
            return True
        return False

45.跳跃游戏2【困难】

LeetCode传送门
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。

思路:
在到达某点前若一直不跳跃,发现从该点不能跳跃到更远的地方,在这之前肯定有次必要的跳跃。也就是说,在无法到达更远的地方时,在这之前应该跳到一个可以到达更远位置的位置。算法:

  • 设置 c u r r e n t _ m a x _ i n d e x current\_max\_index current_max_index当前可达到的最远位置(最小的跳跃次数下);
  • 设置 p r e _ m a x _ i n d e x pre\_max\_index pre_max_index 为在遍历各个位置的过程中,各个位置可达到的最远位置
  • 设置 j u m p _ m i n jump\_min jump_min 为最少跳跃次数;
  • 利用 i i i 遍历数组 n u m s nums nums,若 i i i 超过 c u r r e n t _ m a x _ i n d e x current\_max\_index current_max_index,也就是说,此时不能再向前走,必须在当前位置及之前的某个位置跳了,这个位置是当前遍历的位置中能达到的最远位置中最大的,执行跳跃次数加1 j u m p _ m i n + = 1 jump\_min+=1 jump_min+=1,更新当前能调到的最远位置 c u r r e n t _ m a x _ i n d e x = p r e _ m a x _ i n d e x current\_max\_index=pre\_max\_index current_max_index=pre_max_index
  • 遍历过程中,若 n u m s [ i ] + i nums[i]+i nums[i]+i更大,更新当前能到达的最远位置 p r e _ m a x _ i n d e x = n u m s [ i ] + i pre\_max\_index=nums[i]+i pre_max_index=nums[i]+i
class Solution:
    def jump(self, nums: List[int]) -> int:
        if len(nums)<2:
            return 0        
        jump_min=1
        cur_max_index=nums[0]
        pre_max_index=nums[0]
        for i in range(len(nums)):
            if i>cur_max_index:
                jump_min+=1
                cur_max_index=pre_max_index
            pre_max_index=max(pre_max_index,nums[i]+i)
        return jump_min

452.用最少数量的箭引爆气球【中等】

LeetCode传送门
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。

一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。

思路:
在这里插入图片描述

  • 对各个气球进行排序,按照气球的左端点从小到大排序;
  • 遍历气球数组,同时维护一个射击区间 [ s h o o t _ b e g i n , s h o o t _ e n d ] [shoot\_begin,shoot\_end] [shoot_begin,shoot_end],在满足可以将当前气球射穿的情况下,尽可能击穿更多的气球,每击穿一个新的气球,更新一次射击区间(保证射击区间可以将新气球也击穿);
  • 如果新的气球没办法被击穿了,则需要增加一名弓箭手,即维护一个新的射击区间将该气球击穿,随后继续遍历气球数组。
class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        if len(points)==0:
            return 0
        points.sort()
        shoot_num=1
        shoot_begin=points[0][0]
        shoot_end=points[0][1]
        for i in range(1,len(points)):
            if shoot_end>=points[i][0]:
                shoot_begin=points[i][0]
                if shoot_end>points[i][1]:
                    shoot_end=points[i][1]
            else:
                shoot_num+=1
                shoot_begin=points[i][0]
                shoot_end=points[i][1]
        return shoot_num

871.最低加油次数【困难】

LeetCode传送门

汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处。
沿途有加油站,每个 station[i] 代表一个加油站,它位于出发位置东面 station[i][0] 英里处,并且有 station[i][1] 升汽油。
假设汽车油箱的容量是无限的,其中最初有 startFuel 升燃料。它每行驶 1 英里就会用掉 1 升汽油。
当汽车到达加油站时,它可能停下来加油,将所有汽油从加油站转移到汽车中。
为了到达目的地,汽车所必要的最低加油次数是多少?如果无法到达目的地,则返回 -1 。
注意:如果汽车到达加油站时剩余燃料为 0,它仍然可以在那里加油。如果汽车到达目的地时剩余燃料为 0,仍然认为它已经到达目的地。

思路:

  • 设置一个最大堆,用来存储经过的加油站的汽油量;
  • 按照从起点至终点的方向,遍历各个加油站之间的距离;
  • 每次需要走两个加油站之间的距离 dist,如果发现汽油不够走距离 dist 时,从最大堆中取出一个油量添加,直到可以足够走距离 dist;
  • 如果把最大堆的汽油都添加仍然不够行进距离 dist,则无法达到终点。
  • 每次遍历时,当前油量startFuel减dist,将当前加油站油量添加至最大堆。
class Solution:
    def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int:
        from heapq import heappush
        from heapq import heappop
        pqueue,ans=[],0
        stations.append([target,0])
        # stations.sort()
        for i in range(len(stations)):
            dist=stations[i][0]-stations[i-1][0] if i!=0 else stations[i][0]
            while pqueue and startFuel<dist:
                startFuel-=heappop(pqueue)
                ans+=1
            if not pqueue and startFuel<dist:
                return -1   
            heappush(pqueue,-stations[i][1])
            startFuel-=dist
        return ans

算法 :LeetCode刷题

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值