贪心算法|豆包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)