好的,阿杰。我们一起来详细分析题目 2086. 喂食仓鼠的最小食物桶数(LeetCode题号 2086):“喂食仓鼠的最小食物桶数”,然后给出 C++ 的贪心解法。你可以根据这个思路改写文案或整理成速查卡片。
题目描述
给定一个字符串 hamsters,长度 n。
hamsters[i] == 'H'表示下标 i 处有一只仓鼠。hamsters[i] == '.'表示下标 i 处是空格。
你需要放置“食物桶”(bucket)在某些空格(即'.'位置),使得每只仓鼠至少能从左或右 相邻位置 拿到一个食物桶。一个桶只能服务它两侧各最多一只仓鼠(按题意是:桶放在 i 的话可喂 i-1 或 i+1 如果有仓鼠)。
要求返回放桶的最小数量;如果无法喂所有仓鼠返回 -1。
贪心思路分析
这是一个字符串 + 贪心的问题。关键在于:尽量将桶“共享”给左右两侧的仓鼠,从而减少桶的数量。我们推荐从左往右遍历字符串,遇到仓鼠时优先考虑将桶放 右侧空位,因为这样后面的仓鼠可能也能受益。具体思路:
- 初始化计数
res = 0。 - 遍历
i = 0 .. n-1:- 如果
hamsters[i] == 'H':这只仓鼠还没被喂。- 首先看右侧:如果
i+1 < n && hamsters[i+1] == '.',那么我们在 i+1 放一个桶。这样这个桶可以喂当前仓鼠(i)和可能 i+2 的仓鼠(如果 i+2 是‘I’)。于是res++,然后我们 跳过 i+2(即令i += 2) 因为我们已在 i+1 放了桶,i+2若为仓鼠可以共用这个桶。 - 否则,看左侧:如果
i-1 >= 0 && hamsters[i-1] == '.',则在 i-1 放一个桶,res++。 - 否则:无法为仓鼠 i 放置桶 => 返回 -1。
- 首先看右侧:如果
- 如果
- 遍历结束后返回
res。
这种策略的直觉:优先在右侧放桶,是因为我们在当前位置的仓鼠右侧空位如果可用,就把桶放在那里,从而可能同时服务自己和下一个仓鼠,减少桶数。若右侧不可,则考虑左侧备用位置。若都不可,则失败。
参考文章里也正是这样描述。 (CSDN博客)
你也可以查看另一篇解答。 (力扣 LeetCode)
C++ 代码模板
下面是一个标准的 C++ 实现(你可以根据自己的风格改写为速查卡片):
class Solution {
public:
int minimumBuckets(string hamsters) {
int n = hamsters.size();
int res = 0;
for (int i = 0; i < n; i++) {
if (hamsters[i] == 'H') {
// 尝试右侧放桶
if (i + 1 < n && hamsters[i+1] == '.') {
res++;
i += 2; // 跳过 i+1 放桶后,i+2 可跳
}
else if (i - 1 >= 0 && hamsters[i-1] == '.') {
// 右侧无法,则考虑左侧放桶
res++;
}
else {
// 无法放桶
return -1;
}
}
}
return res;
}
};
几点说明:
i += 2;的使用:当我们在i+1放桶之后,我们知道仓鼠 i 被喂了,同时i+1是桶位置,不是仓鼠位置,所以下一可能未被喂的仓鼠最早在i+2。跳过i+1与i+2(i+2 实际会在下一循环 i++ 到 i+2)可以避免重复检查。- 在放桶之后,不修改字符串,而是只通过控制
i跳跃来保证不会为已经处理的仓鼠重复计数。 - 此方法从左向右一次遍历,时间复杂度 O(n),空间 O(1)。
复杂度分析
- 时间复杂度:O(n),n=字符串长度。
- 空间复杂度:O(1),除输入外只用了常量额外空间。
- 正确性:贪心选择“遇 H 优先右侧桶”是安全的,因为右侧桶能够覆盖当前位置的仓鼠且最大化覆盖范围(未来可能覆盖下一只仓鼠),从而实现最少桶。若右侧不可用,再考虑左侧,否则失败。
注意事项 & 边界情况
- 当字符串长度为 0 或无仓鼠时,返回 0。
- 当字符串某处为
'H'且左右都不是'.'(例如边界'H'和'H'相邻或者无空点),就返回 -1。
例:"HH"、"H"(当只有一个 H 且左右无 ‘.’) => -1。 - 一定注意跳跃逻辑:在右侧放桶后跳过适当位置,防止重复计数或错误位置。
- 当在左侧放桶时,不跳跃,因为左侧桶放在
i-1,但i+1可能仍是仓鼠未被喂,需要在下一个循环继续检查。
发表回复