难度参考
难度:困难
分类:贪心算法
难度与分类由我所参与的培训课程提供,但需 要注意的是,难度与分类仅供参考。且所在课程未提供测试平台,故实现代码主要为自行测试的那种,以下内容均为个人笔记,旨在督促自己认真学习。
题目
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子i,都有一个胃口值g[j],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子i,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例1:输入:g =[1,2,3], s=[1,1]
输出:1
解释:你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。所以你应该输出1。
示例2:输入:g =[1,2], s =[1,2,3]输出:2
解释:你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出 2.
提示:1 <= g.length <= 3 * 1^0(4)
0<= s.length <= 3 * 10~(4)
1 <=g[i],s[j]<= 2^(31)1
思路
解决这个问题的关键是采用贪心算法。贪心算法的基本思想是在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优解出发来考虑做出的选择,而是从某种意义上保证局部是最优的。
对于这个问题,局部最优的策略是“尽量用更小的饼干去满足胃口较小的孩子”,这样做的目的是尽可能多地满足更多的孩子。具体实现策略如下:
1. 排序:首先,将孩子们的胃口值数组`g`和饼干尺寸数组`s`分别进行升序排序。排序的目的是让我们能够从最小的胃口值和最小的饼干尺寸开始尝试配对,这样可以保证使用尽可能小的饼干去满足孩子的胃口。
2. 双指针遍历:使用两个指针`child`和`cookie`分别指向`g`和`s`的起始位置。遍历这两个数组,尽量为每个孩子分配一块能满足其胃口的饼干。
3. 分配饼干:对于每个孩子,从当前可用的最小饼干开始尝试,如果这块饼干的尺寸大于或等于孩子的胃口值,那么就把这块饼干分配给这个孩子,然后移动`child`和`cookie`指针到下一位置,表示这个孩子已经被满足,同时这块饼干也被使用了。如果当前的饼干无法满足当前的孩子,那么只移动`cookie`指针,尝试下一块更大的饼干。
4. 统计满足的孩子数量:重复上述过程,直到所有的饼干都尝试过,或者所有的孩子都被满足。最终,`child`指针所指向的位置即为满足的孩子数量。
通过上述策略,我们可以确保在有限的饼干资源下,尽可能多的孩子被满足,从而达到问题的要求。这种方法的优势在于它简单直观,且能够有效地解决问题。
示例
在这个例子中,我们有三个孩子,他们的胃口值分别是 g = [1, 2, 3],和两块饼干,尺寸分别是 s = [1, 1]。
首先,我们对孩子的胃口值和饼干尺寸进行排序,但在这个例子中,它们已经是排好序的。
我们的目标是找到一种分配方式,使得尽可能多的孩子通过吃饼干感到满足。
初始状态:
- 孩子的胃口值:g = [1, 2, 3]
- 饼干尺寸:s = [1, 1]
- 当前考虑的孩子(child)索引:0
- 当前考虑的饼干(cookie)索引:0
分配过程:
1. 第一轮分配:
- child = 0,cookie = 0:第一块饼干尺寸为1,可以满足胃口为1的第一个孩子。
- 分配后,child和cookie都加1,变为child = 1,cookie = 1。
2. 第二轮分配:
- child = 1,cookie = 1:第二块饼干尺寸也为1,但是无法满足胃口为2的第二个孩子。
- 这里我们发现已经没有更多的饼干可以尝试分配给当前孩子,因此分配过程结束。
结果:
- 成功满足的孩子数量为1(child的最终值为1,表示我们成功满足了从索引0开始的1个孩子)。
- 我们尝试了所有可以分配的饼干(cookie的最终值为1,表示我们尝试了从索引0开始的2块饼干)。
通过这个过程,我们看到在有限的饼干资源下,我们尽可能地满足了孩子们。在这个特定的例子中,我们只能满足1个孩子。这个例子展示了贪心算法如何在每一步做出当前最优的决策,尽管最终可能并不是所有的孩子都能被满足。
梳理
使用贪心算法的核心思想体现在每一步都尝试用最小的资源去满足最容易满足的需求。具体来说,贪心算法的应用体现在以下几个方面:
1. 局部最优选择:在每一步分配中,我们总是尝试用当前可用的最小尺寸的饼干去满足胃口最小的孩子。这是因为使用较小的饼干满足较小的需求可以保留更大的饼干去尝试满足更大的需求,这是一种局部最优的选择。
2. 排序:通过对孩子的胃口值和饼干的尺寸进行排序,我们能够更高效地实施这个策略。排序确保了我们总是从胃口最小的孩子和最小的饼干开始尝试匹配,这样可以最大化满足孩子的数量。
3. 简单贪心规则:尽可能满足更多的孩子的规则非常简单,但却有效。我们没有尝试去预测未来的分配情况或者回溯以寻找更好的分配方案,而是坚持在每一步做出当前看来最好的决策。
4. 无需回溯:贪心算法的一个特点是它不涉及回溯。一旦一块饼干被分配给了一个孩子,我们就不再考虑其他的分配方式。这简化了问题的解决方案,虽然这意味着我们可能错过了全局最优解,但在很多情况下,贪心算法能够提供足够好的解,特别是在资源有限的情况下。
在这个例子中,贪心算法的应用使我们能够在有限的资源下尽可能满足更多孩子的需求,这是通过每一步都做出局部最优选择来实现的。尽管贪心算法不总能保证找到全局最优解,但它的效率和实现的简便性使其成为许多问题的有用工具。
代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int findContentChildren(vector<int>& g, vector<int>& s) {
// 对孩子的胃口值和饼干的尺寸进行排序
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int child = 0, cookie = 0;
// 当孩子或饼干有一个遍历完时停止
while (child < g.size() && cookie < s.size()) {
// 如果当前饼干能满足当前孩子的胃口
if (g[child] <= s[cookie]) {
// 分配饼干给孩子
child++;
}
// 尝试下一块饼干
cookie++;
}
// 返回满足的孩子数量
return child;
}
int main() {
vector<int> g1 = {1, 2, 3};
vector<int> s1 = {1, 1};
cout << "Example 1: " << findContentChildren(g1, s1) << endl; // 输出应为1
vector<int> g2 = {1, 2};
vector<int> s2 = {1, 2, 3};
cout << "Example 2: " << findContentChildren(g2, s2) << endl; // 输出应为2
return 0;
}
时间复杂度:O(nlogn)
空间复杂度:O(1)