Leetcode_贪心算法

455.分发饼干(贪心问题)


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

注意:

一个小朋友最多只能拥有一块饼干。

example:

输入: [1,2], [1,2,3]

输出: 2

解释: 
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.

链接:https://leetcode-cn.com/problems/assign-cookies

解题思路

  • 贪心算法,每次用最小物资满足最小的需求。

步骤:

    1. 重排物资与需求
    1. 从小到大比对物资&需求
    1. 需求满足,两者++一位,需求不满足,物资++一位

代码

class Solution(object):
    def findContentChildren(self, g, s):
        g.sort()
        s.sort()
        i,j = 0,0
        while i < len(g) and j < len(s):
            if g[i] <= s[j]:
                i += 1
                j += 1
            else:
                j += 1
        return i

435. 无重叠区间


给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

链接:https://leetcode-cn.com/problems/non-overlapping-intervals/

example:

输入: [ [1,2], [1,2], [1,2] ]

输出: 2

解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

解题思路

  • 贪心算法,每次保证最小量的局部最优解,结果就是全局最优解
    1. 将所有区间按右边界排序
    1. curr区间作为指针,cur_end从负无穷开始(float("-inf"))
    1. 只要cur_end<=区间左边界,该区间保留,且替换cur_end为该区间右边界

代码

class Solution(object):
    def eraseOverlapIntervals(self, intervals):
        intervals.sort(key=lambda x:x[1])
        cur_end = float("-inf")
        num = 0
        for start,end in intervals:
            if cur_end <= start:
                cur_end = end
                num += 1
            else:
                pass
        return len(intervals) - num

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


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

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

链接:https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons

输入:
[[10,16], [2,8], [1,6], [7,12]]

输出:
2

解题思路

  • 贪心算法,以区间结尾排序
  • 如果cur_end 小于 下一个的start,那箭数+1。
  • 注意:要讨论points为0的情况

代码

class Solution(object):
    def findMinArrowShots(self, points):
        """
        :type points: List[List[int]]
        :rtype: int
        """
        if not points:
            return 0
            
        points.sort(key=lambda x:x[1])
        cur_end = points[0][1]
        arrows = 1
        for begin,end in points:
            if cur_end < begin:
                arrows += 1
                cur_end = end
            else:
                pass
        return arrows

406. 根据身高重建队列


假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。

链接:https://leetcode-cn.com/problems/queue-reconstruction-by-height

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

输出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

解题思路

  • 因为低身高的插入对高身高类间顺序无任何影响,所以
  • 1.先按照身高降序,序号升序排列。
  • 2.把同一身高的,按照序号插入对应队列序号中即可
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gNniDkIf-1585574723920)(https://pic.leetcode-cn.com/f29bc8e24584fa239576933b62b20b61ad3748f91a02a590283c263f1d7ccf62-image.png)]

代码

class Solution(object):
    def reconstructQueue(self, people):
        people.sort(key=lambda x:(-x[0],x[1]))
        output = [] 
        for p in people:
            output.insert(p[1],p)
        return output

121.买卖股票的最佳时机(一次遍历法&双指针法)


给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

注意:你不能在买入股票前卖出股票。

链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock

  • 示例:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

一次遍历法:

  • 遍历每天价格
  • 用minprices记录当天是否为之前的最低价,然后用maxprofit来记录之后的价格差是否为最大利润。
  • p.s 这里有一个时间线的概念,很多人说如果最后一天是最低价怎么办其实是不对的,因为买入必须必卖出早。minpices记录的是相对之前的最低价即可。
  • 时间复杂度O(n):一共就遍历一次,跟列表长度n有关
  • 空间复杂度O(1):常数个变量

代码

class Solution(object):
    def maxProfit(self, prices):
        minprices = int(1e9) #1e9 = 10^9
        maxprofit = 0
        for price in prices:
            maxprofit = max(price - minprices,maxprofit)
            minprices = min(price,minprices)
        return maxprofit
        

暴利循环法

  • 双指针吧,超时了。i从左,j从右。
  • 每一个i值时,j遍历整个列表找最大值
  • 时间复杂度O(n^2):i需要循环n-1次,j需要循环n-i-1次,相数是1,所以总共循环:(n-1+1)*(n-1)/2
  • 空间复杂度O(1):只用了常数个变量
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        ans = 0
        for i in range(len(prices)):
            for j in range(i + 1, len(prices)):
                ans = max(ans, prices[j] - prices[i])
        return ans

122. 买卖股票的最佳时机 II


给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii

  • 示例
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

解题思路

  • 因为可以同时买出买入(当天卖了当天又买),要获得最大收益,只用保证每笔都赚钱就行
  • 即prices[i-1]<prices[i]
  • e.g 把题目当成简单的数学问题去计算最大值就好,不懂就画图。不要被题干代入成文字理解题。

代码

class Solution(object):
    def maxProfit(self, prices):
        maxprofit = 0
        for i in range(1,len(prices)):
            if prices[i-1] < prices[i]:
                maxprofit += prices[i] - prices[i-1]
        return maxprofit

605. 种花问题


假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。

给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 n 。能否在不打破种植规则的情况下种入 n 朵花?能则返回True,不能则返回False。

链接:https://leetcode-cn.com/problems/can-place-flowers

输入: flowerbed = [1,0,0,0,1], n = 1
输出: True

解题思路

  • 题干解读后就一个条件:三连0位,输出+1
  • 首尾补零解决边界问题,用于判断i=0&i=len(flowerbed)位是否可以种花

代码

class Solution(object):
    def canPlaceFlowers(self, flowerbed, n):
        newflowerbed = [0] + flowerbed + [0]
        num = 0
        i = 1
        while i <= len(newflowerbed)-2: 
            if newflowerbed[i-1]==0 and newflowerbed[i]==0 and newflowerbed[i+1]==0:
                i += 2
                num += 1
            else:
                i += 1
        return num >= n

392. 判断子序列


给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

例如,"ace"是"abcde"的一个子序列,而"aec"不是

链接:https://leetcode-cn.com/problems/is-subsequence

解题思路

  • 顺序固定,判断s[i]==t[j]即可
  • 时间复杂度O(n^2)
  • 空间复杂度O(1)

代码

class Solution(object):
    def isSubsequence(self, s, t):
        i,j = 0,0
        while i < len(s) and j < len(t):
            if s[i] == t[j]:
                i += 1
                j += 1
            else:
                j += 1
        return i==len(s)

665. 非递减数列


给你一个长度为 n 的整数数组,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列。

我们是这样定义一个非递减数列的: 对于数组中所有的 i (1 <= i < n),总满足 array[i] <= array[i + 1]。

链接:https://leetcode-cn.com/problems/non-decreasing-array

解题思路

  • 分析题目:当出现nums[i-1]>nums[i]时,改变nums[i-1] or nums[i],可以使数列只通过一次变成非递减。
  • 方案:因为从前往后遍历,使nums[i-1] = nums[i]可以不干扰后面的结果。
  • 一种例外情况:如果nums[i-1]>nums[i-2]>nums[i],只能使nums[i]=nums[i-1]
  • count记录变化次数,i从1开始遍历不然out of range

代码

class Solution(object):
    def checkPossibility(self, nums):
        count = 0
        for i in range(1,len(nums)):
            if nums[i-1] > nums[i]:
                if i-2 >= 0:
                    if nums[i-2] <= nums[i]:
                        nums[i-1] = nums[i]
                    else:
                        nums[i] = nums[i-1]
                else:
                    nums[i-1] = nums[i]
                count += 1
        return count <= 1

53.最大子序和


给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

连接:https://leetcode-cn.com/problems/maximum-subarray/

example:

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

解题思路

  • 贪心算法,每次都把最好的树叶(max_sum)拿在手里
  • 用curr_sum去判断当前数字nums[i]是否直接大于前面数列的和,大于就替代。
  • 再拿curr_sum与max_sum对比是否优于。
  • 时间复杂度O(n):只遍历一次
  • 空间复杂度O(1):常数空间

代码

class Solution(object):
    def maxSubArray(self, nums):
        curr_sum = max_sum = nums[0]
        for i in range(1,len(nums)):
            curr_sum = max(nums[i] , curr_sum+nums[i])
            max_sum = max(curr_sum,max_sum)
        return max_sum

763.划分字母区间


字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一个字母只会出现在其中的一个片段。返回一个表示每个字符串片段的长度的列表。

链接:https://leetcode-cn.com/problems/partition-labels

example:

输入: S = "ababcbacadefegdehijhklij"
输出: [9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。

解题思路

  • 题目解读:最多片段,且同一字母只在一个片段。等价于从左第一位a,找最后a所在位置。中间片段即为最小片段
  • 难点:片段中间的其他字母会扩展区间右边界。
  • 解决问题:右边界什么时候截止呢?
  • 对于每一字母,去找这个字母最后一次出现位置用来更新区间右边界。遍历到右边界时,该片段就截止。

1.用字典+枚举法来更新S的最大下标:last{a:b for a,b in enumerate(S)},这段代码理解为:

last = {}
for a,b in enumerate(S):
    last[b] = a

print last
》 {u'a': 8, u'c': 7, u'b': 5, u'e': 15, u'd': 14, u'g': 13, u'f': 11, u'i': 22, u'h': 19, u'k': 20, u'j': 23, u'l': 21}

2.用start,end表示当前片段首尾,若字母最大下标大于end,则扩大end=last[j],当i遍历到end时结束当前片段,进入下一个。

复杂度分析

  • 时间复杂度O(N)
  • 空间复杂度O(N))

代码

class Solution(object):
    def partitionLabels(self, S):
        last{a:b for a,b in enumerate(S)}
        ans = []
        start,end = 0,0
        for i,j in enumerate(S):
            end = max(end,last[j])
            if i == end:
                ans.append(i - start + 1)
                start = i + 1
        return ans
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值