【贪心】算法思想,附两道道手撕题

8 阅读8分钟

贪心思想是一种解决问题的方法,它通过在每一步选择中都采取最好或者最优的选择,以期望最终结果也是最好或者最优的。

这种思想简单直接,但在某些问题上却能发挥出惊人的效果。

贪心思想的定义

贪心算法是一种在每一步选择中都采取局部最优解的算法,希望这些局部最优解最终能够累积成全局最优解。

然而,需要注意的是,贪心算法得到的结果并不总是最优的,有时候它只能提供近似最优解。

贪心思想的性质

最优子结构性质

最优子结构性质是贪心算法能够工作的基础。

它指的是一个问题的最优解包含其子问题的最优解。这意味着,通过解决子问题,我们可以构建出整个问题的最优解。

这种性质使得贪心算法能够以递归的方式,从局部到全局,逐步构建出解决方案。

贪心选择性质

贪心选择性质是贪心算法的核心。

在每一步选择中,算法都会采取当前看来最好的选择,而不考虑这个选择对未来的影响。

这种局部最优的选择策略,是贪心算法追求全局最优解的关键。

在选择使用贪心算法时,必须确保问题满足这两个性质,否则贪心算法可能无法得到正确的结果。

贪心思想的应用场景

贪心算法因其简单和高效,在多个领域都有广泛的应用。以下是一些典型的应用场景:

  1. 排序问题:选择排序和拓扑排序是贪心算法在排序问题中的典型应用。
  2. 优先队列:堆排序利用贪心思想,通过维护一个优先队列来实现高效的排序。
  3. 赫夫曼压缩编码:在数据压缩领域,贪心算法被用来构建最优的前缀编码。
  4. 图算法:在图论中,Prim、Kruskal和Dijkstra算法都是基于贪心思想的著名算法。
  5. 硬币找零问题:在货币系统中,贪心算法可以被用来解决如何用最少数量的硬币凑成特定金额的问题。
  6. 部分背包问题:在资源有限的情况下,贪心算法可以帮助我们选择价值最大的物品。
  7. 并查集:在并查集中,按大小或者高度合并问题或者排名问题都可以应用贪心算法。
  8. 任务调度:在某些任务调度场景中,贪心算法可以帮助我们找到最优的任务执行顺序。
  9. 复杂问题的近似算法:对于一些难以找到精确解的复杂问题,贪心算法可以提供有效的近似解。

算法

社交距离

描述

疫情期间需要大家保证一定的社交距离,公司组织开交流会议。座位一排共 N 个座位,编号分别为[0,N-1],

要求员工一个接着一个进入会议室,并且可以在任何时候离开会议室。

满足:

  • 每当一个员工进入时,需要坐到最大社交距离(最大化自己和其他人的距离的座位);
  • 如果有多个这样的座位,则坐到索引最小的那个座位。
输入描述

会议室座位总数 seatNum

  • 1 ≤ seatNum ≤ 500

员工的进出顺序 seatOrLeave 数组

  • 元素值为 1,表示进场

  • 元素值为负数,表示出场(特殊:位置 0 的员工不会离开)

    例如 -4 表示坐在位置 4 的员工离开(保证有员工坐在该座位上)

输出描述

最后进来员工,他会坐在第几个位置,如果位置已满,则输出-1。

题解

座位分配策略:

  1. 初始化座位占用列表:  我们使用一个动态数组 seat 来追踪当前被占用的座位编号。

  2. 处理员工进出:  对于给定的操作序列 seatOrLeave,我们按顺序处理每个操作:

    • 员工进入(正数操作):

      • 如果 seat 数组为空,表示会议室无占用,新员工将被安排在0号座位。
      • 如果 seat 数组非空,我们计算所有已占用座位之间的间隙大小,并找出最大的间隙。新员工将被安排在最大间隙中的第一个可用座位。
    • 员工离开(负数操作):  根据操作值的绝对值确定离开员工的座位编号,并从 seat 数组中移除该编号。

  3. 更新座位占用状态:  对于新进入的员工,我们将其座位编号添加到 seat 数组,并保持 seat 数组有序,以便于快速定位下一个最佳座位。

  4. 输出最终座位编号:  处理完所有操作后,我们输出最后一个进入会议室的员工的座位编号。如果会议室已满,则输出 -1

import math

# 读取输入
seatNum = int(input())  # 座位总数
str = input()  # 座位占用和离开的操作序列
seatOrLeave = list(map(int, str[1:-1].split(",")))  # 将字符串转换为整数数组

# 初始化
seat = []  # 存储已占用的座位
ans = -1  # 下一个人的最佳座位号

# 遍历座位占用和离开的操作序列
for sol in seatOrLeave:
    if sol == 1:
        # 员工进入
        if not seat:
            # 如果会议室为空,新员工坐在0号座位
            ans = 0
        else:
            # 如果会议室不为空,找到最大的空闲区间
            max_distance = seat[0]  # 初始化最大距离为第一个座位号
            ans = 0  # 初始化最佳座位号为0
            for i in range(len(seat)):
                if i == len(seat) - 1:
                    # 检查最后一个座位到座位总数之间的距离
                    distance = seatNum - 1 - seat[i]
                else:
                    # 检查相邻座位之间的距离
                    distance = (seat[i + 1] - seat[i]) // 2
                if distance > max_distance:
                    # 更新最大距离和最佳座位号
                    max_distance = distance
                    if i == len(seat) - 1:
                        ans = seat[i] + distance
                    else:
                        ans = seat[i] + distance

        # 如果会议室已满,输出-1
        if len(seat) < seatNum:
            # 将新员工的座位号加入到座位列表,并排序
            seat.append(ans)
            seat.sort()
        else:
            ans = -1
    else:
        # 员工离开
        sol = -sol  # 取绝对值
        if sol in seat:
            seat.remove(sol)
            # 重新计算最佳座位号
            if seat:
                ans = seat[0]  # 如果还有座位被占用,最佳座位号为第一个座位
            else:
                ans = 0  # 如果没有座位被占用,最佳座位号为0

print(ans)

环中最长子串字符成环找偶数O

描述

给你一个字符串 s,字符串s首尾相连成一个环形 ,请你在环中找出 ‘o’ 字符出现了偶数次最长子字符串的长度。

输入描述

输入是一串小写字母组成的字符串

备注

1 <= s.length <= 5 x 10^5
s 只包含小写英文字母

输出描述

输出是一个整数

题解

我们可以将问题分为两种情况来考虑:

  1. 偶数情况:

    • 当字符串中 'o' 的总数为偶数时,这意味着我们可以在任意子字符串中找到偶数个 'o'。这是因为任何子字符串都可以看作是原字符串的一部分,而原字符串中 'o' 的总数是偶数,所以任何子字符串中的 'o' 总数也必然是偶数或零(如果子字符串中没有 'o')。因此,整个字符串本身就是一个符合条件的子字符串,因为它包含了偶数次的 'o'。
  2. 奇数情况:

    • 当字符串中 'o' 的总数为奇数时,我们的目标是找到一个包含偶数次 'o' 的最长子字符串。由于 'o' 的总数是奇数,任何包含所有 'o' 的子字符串都不可能满足条件(因为它将包含奇数个 'o')。因此,我们必须至少去掉一个字符,使得剩余的子字符串中 'o' 的数量变为偶数。由于我们只能去掉一个字符,最长可能的子字符串长度就是原字符串长度减一(len - 1)。

这种方法的有效性基于对字符串中 'o' 出现次数的奇偶性分析,以及对子字符串中 'o' 出现次数的影响。

通过简单地检查 'o' 的总数,我们可以快速确定是否需要去掉一个字符,以及最长可能的子字符串长度。这种方法简洁且高效,因为它避免了对所有可能的子字符串进行逐一检查,而是直接根据 'o' 的总数来确定答案。

读取用户输入的字符串

input_str = input("")

获取字符串的长度

len_str = len(input_str)

初始化'o'字符的计数器

num = 0

遍历字符串,统计'o'字符的数量

for chr in input_str: if chr == 'o': num += 1

如果'o'字符出现的次数是偶数,则输出字符串的长度

if num % 2 == 0: print(len_str) else: # 如果'o'字符出现的次数是奇数,则输出字符串长度减1 print(len_str - 1)