秋招算法备战第35天 | 860.柠檬水找零、406.根据身高重建队列、452. 用最少数量的箭引爆气球

860. 柠檬水找零 - 力扣(LeetCode)

此问题可以通过维护每种面值钞票的数量来解决。当我们收到一张新的钞票时,我们必须确保我们有足够的零钱来找给顾客。以下是一个Python函数,用于解决这个问题:

def lemonadeChange(bills):
    five, ten = 0, 0 # 初始化5美元和10美元的钞票数量

    for bill in bills:
        if bill == 5:
            five += 1 # 收到5美元,直接存起来
        elif bill == 10:
            if five == 0: # 如果没有5美元,则无法找零
                return False
            five -= 1 # 找给顾客5美元
            ten += 1  # 收取顾客的10美元
        else: # bill为20美元的情况
            # 首先尝试使用一张10美元和一张5美元的组合
            if ten > 0 and five > 0:
                ten -= 1
                five -= 1
            # 如果没有10美元的钞票,则尝试使用三张5美元的组合
            elif five >= 3:
                five -= 3
            else: # 如果都不满足,则无法找零
                return False

    return True # 所有顾客都找零成功

# 示例
bills1 = [5,5,5,10,20]
print(lemonadeChange(bills1)) # 输出:True

bills2 = [5,5,10,10,20]
print(lemonadeChange(bills2)) # 输出:False

此解决方案的时间复杂度为O(n),其中n为bills的长度。

406. 根据身高重建队列 - 力扣(LeetCode)

当然!下面是对问题"406. 根据身高重建队列"的解决思路和代码的总结:

思路

  1. 排序:首先,我们需要对人按照特定的顺序进行排序。我们将人按照身高降序排序,如果身高相同,则按照前面的人数升序排序。这样可以确保我们先处理身高较高的人,然后再处理身高较低的人。

  2. 插入:然后,我们遍历排序后的人,并按照每个人的k值(即前面有多少个更高或相同身高的人)将其插入到队列中的相应位置。

代码

以下是实现上述思路的代码:

def reconstructQueue(people):
    # 按身高降序,k值升序排序
    people.sort(key=lambda x: (-x[0], x[1]))
    
    # 初始化空队列用于保存结果
    queue = []

    # 遍历排序后的人
    for p in people:
        # 在队列中的位置p[1]处插入人p
        queue.insert(p[1], p)

    return queue

# 示例
people1 = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
print(reconstructQueue(people1))
# 输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]

总结

这个问题的关键在于如何找到合适的插入位置,以确保每个人前面有正确数量的更高或相同身高的人。排序策略允许我们以一种结构化的方式进行插入,从而构造符合条件的队列。整体解决方案的时间复杂度是O(n^2),因为对于每个人,插入操作可能需要O(n)的时间,其中n是人的数量。

452. 用最少数量的箭引爆气球 - 力扣(LeetCode)

这个问题可以使用贪心算法来解决。下面是解决问题的一种方法:

  1. 首先,按照气球的结束坐标(xend)对气球进行排序。
  2. 初始化箭的数量为0,并从左至右扫描所有气球。
  3. 将第一支箭射在第一个气球的结束坐标(即xend)上,击破这个气球。
  4. 遍历剩下的气球,如果气球的开始坐标(即xstart)小于等于当前箭的位置,那么这支箭也能击破这个气球,所以无需添加新的箭。如果气球的开始坐标大于当前箭的位置,则需要射出一支新的箭,且这支箭的位置就是这个气球的结束坐标。
  5. 重复第4步,直到所有气球被击破。

以下是相应的代码实现:

def findMinArrowShots(points):
    if not points:
        return 0
    
    # 按照气球的结束坐标排序
    points.sort(key=lambda x: x[1])
    
    # 初始化箭的数量为1,并选择第一个气球的结束坐标作为箭的位置
    arrows = 1
    arrow_position = points[0][1]
    
    # 遍历剩余的气球
    for i in range(1, len(points)):
        # 如果气球的开始坐标大于当前箭的位置,射出一支新的箭
        if points[i][0] > arrow_position:
            arrows += 1
            arrow_position = points[i][1]
    
    return arrows

这个解决方案的时间复杂度是O(n log n),因为需要对气球按照结束坐标进行排序。空间复杂度是O(1)。

452. 用最少数量的箭引爆气球 - 力扣(LeetCode)

这个问题可以使用贪心算法来解决。下面是解决问题的一种方法:

  1. 首先,按照气球的结束坐标(xend)对气球进行排序。
  2. 初始化箭的数量为0,并从左至右扫描所有气球。
  3. 将第一支箭射在第一个气球的结束坐标(即xend)上,击破这个气球。
  4. 遍历剩下的气球,如果气球的开始坐标(即xstart)小于等于当前箭的位置,那么这支箭也能击破这个气球,所以无需添加新的箭。如果气球的开始坐标大于当前箭的位置,则需要射出一支新的箭,且这支箭的位置就是这个气球的结束坐标。
  5. 重复第4步,直到所有气球被击破。

以下是相应的代码实现:

def findMinArrowShots(points):
    if not points:
        return 0
    
    # 按照气球的结束坐标排序
    points.sort(key=lambda x: x[1])
    
    # 初始化箭的数量为1,并选择第一个气球的结束坐标作为箭的位置
    arrows = 1
    arrow_position = points[0][1]
    
    # 遍历剩余的气球
    for i in range(1, len(points)):
        # 如果气球的开始坐标大于当前箭的位置,射出一支新的箭
        if points[i][0] > arrow_position:
            arrows += 1
            arrow_position = points[i][1]
    
    return arrows

这个解决方案的时间复杂度是O(n log n),因为需要对气球按照结束坐标进行排序。空间复杂度是O(1)。

这个问题要求我们找到引爆所有气球所必须射出的最小弓箭数。我们可以通过以下方式解释为什么上述贪心算法能够解决这个问题:

  1. 排序:通过按结束坐标xend对气球进行排序,我们确保了气球是按其水平位置的顺序考虑的。这样,我们可以从左至右扫描所有气球,并确保在当前箭的位置能击破尽可能多的气球。

  2. 最优选择:我们始终射出箭以击破当前未击破气球的最左边的气球。这是有意义的,因为如果箭可以击破该气球,那么它还可以击破与该气球重叠的所有气球。将箭射在结束坐标(即xend)上确保了能够击破所有与当前气球重叠的气球。

  3. 贪心选择属性:选择当前未击破气球的最左边的气球,并在其结束坐标射箭,是一个局部最优的选择。这个选择能够确保当前箭可以击破尽可能多的气球。通过逐步做出这样的局部最优选择,我们将得到全局最优解。

  4. 没有回溯:一旦箭被射出,我们不需要重新考虑这个决策。我们只需考虑哪些气球还没有被击破,然后继续射箭。我们不需要考虑以前的决策,因为按照结束坐标排序并选择结束坐标射箭确保了每一步都是最优的。

通过上述方法,该算法能够在有限的步骤内找到引爆所有气球所必须射出的最小弓箭数,并确保这个解是最优的。

总结

“根据身高重建队列”和“用最少数量的箭引爆气球”都是通过排序使得能够通过贪心一次遍历解决问题

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