贪心算法|豆包MarsCode AI刷题

56 阅读4分钟

贪心算法|豆包MarsCode AI刷题

一、定义

贪心算法(又称贪婪算法),在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解 。它的根本思想是逐步到达山顶,即逐步获得最优解,是解决最优化问题时的一种简单但是适用范围有限的策略。 贪心算法不是对所有问题都能得到体最优解,关键是整贪心策略的选择。贪心策略要无后向性,也就是说某状态以后的过程不会影响以前的状态,只与当前状态有关。

贪心算法一般按如下步骤进行:

①建立数学模型来描述问题;

②把求解的问题分成若干个子问题;

③对每个子问题求解,得到子问题的局部最优解;

④把子问题的解局部最优解合成原来解问题的一个。

二、全局最优解

决定一个贪心算法是否能找到全局最优解的条件主要为以下两点:

  • 最优子结构(optimal subproblem structure,和动态规划中的是一个概念)
  • 最优贪心选择属性(optimal greedy choice property)

三、选择使用

贪心算法的原理是通过局部最优来达到全局最优,采用的是逐步构造最优解的方法。在每个阶段,都做出一个看上去最优的,决策一旦做出,就不再更改。

要选出最优解可不是一件容易的事,要证明局部最优为全局最优,要进行数学证明,否则就不能说明为全局最优。

在很多大规模问题中,寻找最优解是一件相当费时耗力的事情,性价比不一定很高,相对最优的贪心算法较为经济可行。

四、题目:字典序最小的01字符串

问题描述

小U拥有一个由0和1组成的字符串,她可以进行最多k次操作,每次操作可以交换相邻的两个字符。目标是通过这些操作,使得最终得到的字符串字典序最小。

例如,小U当前有一个字符串 01010,她最多可以进行 2 次相邻字符交换操作。通过这些操作,她可以将字符串调整为 00101,这是可以通过不超过2次操作得到的字典序最小的字符串。

现在,小U想知道,经过最多k次操作后,能够得到的字典序最小的字符串是什么。

测试样例

样例1:

输入:n = 5, k = 2, s = "01010"
输出:'00101'

样例2:

输入:n = 7, k = 3, s = "1101001"
输出:'0110101'

样例3:

输入:n = 4, k = 1, s = "1001"
输出:'0101'

思路

尝试通过一系列的交换操作,使得字符串中的'1'尽可能地向左移动,同时确保'0'在它们右边。

  • 在每次循环中,检查当前位置i的字符是否为'1',并且是否还有剩余的操作次数k
  • 如果当前位置是'1'并且还有剩余的操作次数,那么尝试找到从当前位置i开始的最靠前的'0'的位置。
  • 如果找到了'0',并且'1'可以移动到'0'的位置(即first_zero - i <= k),则交换这两个位置的字符,并且减少k的值。
  • 如果'1'不能完全移动到'0'的位置,那么代码会尝试尽可能地将'1'向左移动。这是通过内层循环实现的,它会检查从当前位置i开始的k次操作范围内是否有'0',如果有,则交换,并减少k的值。

这种贪心策略的核心在于每一步都最大化'1'的左移,同时保持'0'在它们右边。缺点是可能不适用于所有类型的字符串操作问题,因为贪心算法并不总是能够保证找到全局最优解。

代码

def solution(n: int, k: int, s: str) -> str:
    s = list(s)  # 将字符串转换为列表以便交换
    
    # 贪心策略:每次找到最靠前的1并将其尽可能地向左移动
    for i in range(n):
        if s[i] == '1' and k > 0:
            # 找到最靠前的0的位置
            first_zero = s.index('0', i) if '0' in s[i:] else n
            
            # 如果1可以移动到0的位置并且还有剩余的操作次数k,则交换它们
            if first_zero < n and first_zero - i <= k:
                s[i], s[first_zero] = s[first_zero], s[i]
                k -= (first_zero - i)
            else:
                # 如果不能完全移动到0的位置,则尽可能向左移动
                # 找到最左边的0的位置
                for j in range(i, min(i + k + 1, n)):
                    if s[j] == '0':
                        s[i], s[j] = s[j], s[i]
                        k -= (j - i)
                        break
    
    # 将列表转换回字符串
    return ''.join(s)