分发饼干问题

127 阅读5分钟

贪心算法在分发饼干问题中的应用解析

一、问题描述

LeetCode455. 分发饼干

“分发饼干” 问题的目标是将饼干分配给孩子,每个孩子只能获得一个饼干,且饼干尺寸需大于等于孩子的胃口值,最终求最多能满足的孩子数量。

在这个问题中

输入为两个数组,数组g记录了孩子的胃口值,数组s记录了饼干的尺寸;输出则是满足条件的最大孩子数量。

例如,当输入g = [1,2]s = [1,2,3]时,我们需要找到一种分配方案,使得满足条件的孩子数量最大化。

二、解题思路

贪心算法解决该问题的核心思想优先用最小的饼干满足最小胃口的孩子,以此来最大化饼干的利用率。具体实现步骤如下:

  1. 排序:首先,将数组gs进行升序排序。排序后的数组,孩子的胃口值从小到大排列,饼干的尺寸也从小到大排列。这样做的目的是确保我们按照 “最小胃口→最大胃口” 和 “最小饼干→最大饼干” 的顺序进行匹配,避免大饼干浪费在小胃口孩子身上,从而实现最优分配。

  2. 双指针遍历:使用双指针法进行遍历。用指针i遍历每个孩子的胃口(从小到大),用指针sIndex遍历饼干(从小到大),找到第一个能满足当前孩子的饼干。在遍历过程中,一旦找到满足条件的饼干,就将其分配给当前孩子,并移动到下一个饼干继续匹配下一个孩子。

  3. 终止条件:设定一个终止条件,若当前孩子的胃口超过最大饼干尺寸,说明后续孩子更无法满足,此时直接退出循环。这样可以避免不必要的计算,提高算法效率。

三、代码实现

以下是使用 JavaScript 实现的代码:

var findContentChildren = function (g, s) {
  // 排序:孩子胃口和饼干尺寸升序排列
  g.sort((a, b) => a - b);
  s.sort((a, b) => a - b);

  let num = 0; // 满足的孩子数量
  let sIndex = 0; // 当前饼干索引

  for (let i = 0; i < g.length; i++) {
    // 优化:若当前孩子胃口超过最大饼干,无需继续遍历
    if (g[i] > s[s.length - 1]) break;

    // 寻找能满足当前孩子的最小饼干
    while (sIndex < s.length) {
      if (s[sIndex] >= g[i]) {
        num++; // 满足一个孩子
        sIndex++; // 移动到下一个饼干
        break; // 跳出循环,处理下一个孩子
      }
      sIndex++; // 饼干太小,尝试下一个
    }
  }
  return num;
};

在这段代码中,首先对两个数组进行排序,然后通过两层循环实现贪心策略。外层for循环遍历孩子数组,内层while循环遍历饼干数组,找到能满足当前孩子的最小饼干,并更新满足孩子的数量和当前饼干索引。

四、关键逻辑说明

  1. 排序的作用:排序是贪心算法能够有效执行的基础。通过升序排序,我们可以保证在匹配过程中,小饼干优先分配给小胃口的孩子,大饼干留给大胃口的孩子,从而避免资源浪费,实现最优分配。

  2. while 循环的意义:内层while循环的作用是遍历饼干数组,为当前孩子找到第一个能满足其胃口的饼干。在循环过程中,sIndex持续后移,一旦找到满足条件的饼干,sIndex不会回退,确保每个饼干只被使用一次,符合题目中每个孩子只能获得一个饼干的要求。

  3. 截断条件if (g[i] > s[s.length - 1]) break;这一条件判断非常关键。当当前孩子的胃口已超过最大饼干尺寸时,由于后续孩子的胃口只会更大,所以无需再继续遍历饼干数组,直接终止循环,大大减少了不必要的计算。

五、复杂度分析

  1. 时间复杂度:算法的时间复杂度为 O(n log n + m log m),其中 n = g.length,m = s.length。这是因为排序操作的时间开销为 O (n log n) 和 O (m log m),而后续的遍历操作时间复杂度相对较低,在整体时间复杂度中占比较小,因此可以忽略不计。

  2. 空间复杂度:空间复杂度为 O(log n + log m),这主要取决于排序算法所需的栈空间。不同的排序算法,其空间复杂度可能有所不同,但一般来说,对于基于比较的排序算法,其空间复杂度通常为 O (log n) 或 O (n)。

六、示例验证

以输入g = [1,2]s = [1,2,3]为例,排序后g = [1,2]s = [1,2,3]。匹配过程如下:

  1. 孩子 1(胃口 1)→ 饼干 1(尺寸 1)→ 满足,num=1,sIndex=1。

  2. 孩子 2(胃口 2)→ 饼干 2(尺寸 2)→ 满足,num=2,sIndex=2。 最终输出为 2,即共满足 2 个孩子。通过这个示例,我们可以更直观地理解贪心算法是如何按照 “最小饼干满足最小胃口” 的策略进行分配的。

    最终输出为 2,即共满足 2 个孩子。通过这个示例,我们可以更直观地理解贪心算法是如何按照 “最小饼干满足最小胃口” 的策略进行分配的。

七、贪心策略有效性证明

为了证明贪心策略的有效性,我们采用反证法。假设存在更优的分配方案,即 “用大饼干满足小胃口孩子”。在这种情况下,原本可以满足更大胃口孩子的小饼干就会被浪费,导致后续更大胃口的孩子无法得到满足,总满足数量不会增加,甚至可能减少。因此,从小到大匹配的贪心策略是最优解