Leetcode贪心

理论

1、什么是贪心:
贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
2、什么时候用贪心:
若能通过局部最优,推出整体最优就使用贪心
3、与动态规划的区别:
从一堆钞票中拿走十张使金额最大——贪心
从一堆不同体积的物品中选若干将背包装满——动态规划
4、贪心问题解法:
想清楚 局部最优 是什么,如果推导出全局最优

题目

455.分发饼干

在这里插入图片描述
思路:对孩子的胃口和饼干的大小由小到大排序,优先喂小胃口的孩子

class Solution:
    def findContentChildren(self, g: List[int], s: List[int]) -> int:
        # 小饼干给小胃口的孩子
        g.sort()
        s.sort()
        child = 0 # 先喂胃口最小的孩子
        for j in range(len(s)):  # 遍历饼干,找到尽可能小的饼干
            if s[j]>=g[child]:
                child += 1  # 当前child找到了适合他的饼干,继续为下一个孩子分饼干
            if child==len(g):  # 已经为所有的孩子找到饼干
                break    
        return child 

## 376. 摆动序列 ![在这里插入图片描述](https://img-blog.csdnimg.cn/dde362af14614c8fb94a51c156c877d2.png) 法1:动态规划
class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        # dp[i][0],dp[i][1]:以nums[i]为峰/谷结尾的摆动序列的最长长度
        n = len(nums)
        dp = [[0]*2 for _ in range(n)]
        dp[0][0] = 1
        dp[0][1] = 1

        for i in range(1,n):
            for j in range(i):
                if nums[j]<nums[i]:
                    dp[i][0] = max(dp[j][1]+1, dp[i][0])                  
                elif nums[j]>nums[i]:
                    dp[i][1] = max(dp[j][0]+1, dp[i][1])
                else:
                    dp[i][0] = max(dp[j][0], dp[i][0])
                    dp[i][1] = max(dp[j][1], dp[i][1])

        return max(dp[-1][0], dp[-1][1])

注:时O(N^2)

法2:找最长摆动子序列长度等效为找求峰谷数+1

class Solution:
    def wiggleMaxLength(self, nums: List[int]) -> int:
        n = len(nums)  
        if n==1:
            return 1
        
        ans = 1  # 第0个点
        for i in range(1,n):            
            if i==1:
                diff = nums[i]-nums[i-1]
                if diff!=0: # diff正负预示上一个是上升还是下降
                    ans += 1                
            else:
                if (nums[i]>nums[i-1] and diff<=0) or (nums[i]<nums[i-1] and diff>=0):
                    ans += 1
                    diff = nums[i]-nums[i-1]
        return ans

注:时O(N) 空O(1)

53. 最大子数组和

在这里插入图片描述

法1:贪心
遍历nums,从头开始用count累积,如果count一旦加上nums[i]变为负数,那么就应该从nums[i+1]开始从0累积count了,因为已经变为负数的count,只会拖累总和。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        # 贪心
        n = len(nums)
        count = 0
        ans = -float('inf')
        for i in range(n):
            count = nums[i] + count
            if count>ans:
                ans = count
            if count<=0:
                count = 0
        return ans

时O(N) 空O(1)
法2:动态规划

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n = len(nums)
        dp = [0]*n
        dp[0] = nums[0]

        ans = dp[0]
        for i in range(1,n):
            dp[i] = max(dp[i-1]+nums[i], nums[i])
            ans = max(ans, dp[i])
        return ans

时O(N) 空O(N) 注(空间可以简化,只用变量)

122. 买卖股票的最佳时机 II (可多次买卖)

法1:贪心 若第i天大于前一天(i-1)的价格,则在前一天买入 ,第i天卖出(局部最优)——所有局部最优的和(全局最优)

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

时:O(N) 空O(1)

法2:动态规划

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        dp = [[0]*2 for _ in range(n)]
        dp[0][0] = -prices[0]

        for i in range(1, n):
            dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i])
            dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i])
        return dp[-1][1] 

55. 跳跃游戏

在这里插入图片描述
这道题目关键点在于:不用拘泥于每次究竟跳几步,而是看覆盖范围,覆盖范围内一定是可以跳过来的,不用管是怎么跳的。
贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围),整体最优解:最后得到整体最大覆盖范围,看是否能到终点。

class Solution:
    def canJump(self, nums: List[int]) -> bool:
        n = len(nums)
        cover = 0
        i = 0
        while i<=cover:
            cover = max(cover,i+nums[i])
            if cover>=n-1:
                return True
            i += 1
        return False 

45. 跳跃游戏 II

在这里插入图片描述

45. 跳跃游戏 II

在这里插入图片描述
要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最小步数!

这里需要统计两个覆盖范围,当前这一步的最大覆盖和下一步最大覆盖

class Solution:
    def jump(self, nums: List[int]) -> int:
        n = len(nums)
        curDistance = 0  # 当前步可覆盖的最远
        nextDistance = 0  # 下一步可覆盖的最远
        ans = 0
        for i in range(n):
            nextDistance = max(nextDistance, i+nums[i]) 
            if i == curDistance:  # 到达当前步数能到达的最远端
                if i>=(n-1):
                    break
                else: 
                    ans += 1  # 还未到终点,步数加1                
                    curDistance = nextDistance
        return ans  

56. 合并区间

在这里插入图片描述

判断区间重叠类的问题,先排序,让所有的相邻区间尽可能的重叠在一起,按左边界,或者右边界排序都可以。如果按照左边界从小到大排序,若有 intervals[i][0] <= intervals[i - 1][1] 即intervals[i]的左边界 <= intervals[i - 1]的右边界,则一定有重叠。(注意intervals[i-1][0]≤intervals[i][0]≤intervals[i][1])

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        intervals.sort(key = lambda x: x[0]) # 按intervals的第0个元素对其排序
        n = len(intervals)
        result = [intervals[0]]  # 初始化
        for i in range(1, n):
            if intervals[i][0]<=result[-1][1]:
                result[-1] = [result[-1][0], max(result[-1][1], intervals[i][1])]
            else:
                result.append(intervals[i])
        return result

类似的题目:
在这里插入图片描述
思路:
在这里插入图片描述
①按右端排序 右端越短,不重叠的区间数越多
②头大于等于已有的尾,说明不重叠,不重叠区间数加一,更新尾位置

class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort(key = lambda x: x[1])  # 按右端排序  右端越短,不重叠的区间数越多
        n = len(intervals)
        nooverlap = 1  # 记录不重合的区间数
        end = intervals[0][1]  # 第一个的尾是初始的尾
        for i in range(1, n):
            if intervals[i][0]>=end:  # 不重叠
                nooverlap += 1
                end = intervals[i][1]  # 更新新的尾
        return n-nooverlap

时O(NlogN) 空(N)——快排

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

class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        points.sort(key = lambda x: x[0])
        n = len(points)
        ans = 1
        for i in range(1, n):
            if points[i][0]<=points[i-1][1]:  #有重合
                points[i][1] = min(points[i][1], points[i-1][1])  # 两尾取其短
            else:  # 和前一个无重合  需另射一箭
                ans += 1
        return ans

注意每段的头是与上一段的尾比较,而若射了箭,上一段的尾应该是两端中较短的。

406.根据身高重建队列

在这里插入图片描述
解法:注:像这种两个量的要先对一个进行排序
在这里插入图片描述

class Solution:
    def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:
        # 先按身高由高到矮排序
        people.sort(key = lambda x: (-x[0], x[1]))  # sort默认从小到大 -x[0]就是由大到小,元组指第一个同就比较第二个
        n = len(people)
        queue = []
        for p in people:
            queue.insert(p[1], p) # 在p[1]位置处插入p
        return queue

1405. 最长快乐字符串

在这里插入图片描述思路:要想长度最长,则每次取个数最多的添入答案; 如果前两位已经和个数最多的一样了,则取次多的添入,如果没有次多的,那就结束了,因为此时剩下的一定都和最多的一样,会出现xxx

class Solution:
    def longestDiverseString(self, a: int, b: int, c: int) -> str:
        ac = [["a", a], ["b", b], ["c", c]]
        ans = []
        flag = True
        while flag:        
            ac.sort(key = lambda x: -x[1])  # -x降序
            for i, (sym, num) in enumerate(ac):  # enumerate 返回索引i及内容(sym,num)
                if len(ans)>=2 and sym == ans[-1] and sym == ans[-2]:  # 当前最大数量的字符和ans中最后两个相同 找次大
                    continue  # 找次大 
                if ac[i][1]>0:  # 当前字符还有字母没用完     
                    ans.append(sym)
                    ac[i][1] -= 1  # 数量减1
                    break
                else:  # 字母全用完 或仅有最多的剩下  已找到最长快乐数 退出
                    flag = False
                    break
        return "".join(ans)
# 时O((a+b+c)ClogC) 其中C是字符种类数  由于每添一个字符就要重新对ac排序
# 空O(C)  存字符及次数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值