title: Assign Cookies
slug: assign-cookies
difficulty: easy
leetcode_id: 455
leetcode_url: https://leetcode.com/problems/assign-cookies/
categories:
  - arrays
  - sorting
  - two-pointers
patterns:
  - slug: greedy
    is_optimal: false
  - slug: two-pointers
    is_optimal: true

function_signature: "def find_content_children(g: list[int], s: list[int]) -> int:"

test_cases:
  visible:
    - input: { g: [1, 2, 3], s: [1, 1] }
      expected: 1
    - input: { g: [1, 2], s: [1, 2, 3] }
      expected: 2
  hidden:
    - input: { g: [1, 2, 3], s: [] }
      expected: 0
    - input: { g: [10, 9, 8, 7], s: [5, 6, 7, 8] }
      expected: 2
    - input: { g: [1, 1, 1, 1], s: [1, 1, 1, 1] }
      expected: 4
    - input: { g: [1], s: [1] }
      expected: 1
    - input: { g: [5], s: [4] }
      expected: 0
    - input: { g: [2, 3, 4], s: [1, 2, 3, 4, 5] }
      expected: 3

description: |
  Assume you are an awesome parent and want to give your children some cookies. But, you should give each child at most one cookie.

  Each child `i` has a greed factor `g[i]`, which is the minimum size of a cookie that the child will be content with; and each cookie `j` has a size `s[j]`. If `s[j] >= g[i]`, we can assign the cookie `j` to the child `i`, and the child `i` will be content.

  Your goal is to maximize the number of your content children and output the maximum number.

constraints: |
  - `1 <= g.length <= 3 * 10^4`
  - `0 <= s.length <= 3 * 10^4`
  - `1 <= g[i], s[j] <= 2^31 - 1`

examples:
  - input: "g = [1,2,3], s = [1,1]"
    output: "1"
    explanation: "You have 3 children and 2 cookies. The greed factors of 3 children are 1, 2, 3. And even though you have 2 cookies, since their size is both 1, you could only make the child whose greed factor is 1 content."
  - input: "g = [1,2], s = [1,2,3]"
    output: "2"
    explanation: "You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2. You have 3 cookies and their sizes are big enough to gratify all of the children."

explanation:
  intuition: |
    Imagine you're distributing cookies to children at a party. Each child has a minimum cookie size they'll accept, and you want to make as many children happy as possible.

    The key insight is that **smaller cookies should go to less greedy children**. If you give a large cookie to a child who would have been happy with a small one, you might waste that large cookie when it could have satisfied a greedier child.

    Think of it like this: sort both the children by their greed and the cookies by their size. Then, starting with the least greedy child and smallest cookie, try to make matches. If the smallest available cookie can satisfy the least greedy unsatisfied child, it's always optimal to make that assignment.

    Why does this greedy approach work? Because giving the smallest sufficient cookie to the least greedy child never makes things worse. That cookie couldn't have satisfied a greedier child anyway (or only barely), and using it frees up larger cookies for children who truly need them.

  approach: |
    We solve this using a **Sort + Two Pointers (Greedy) Approach**:

    **Step 1: Sort both arrays**

    - Sort `g` (greed factors) in ascending order
    - Sort `s` (cookie sizes) in ascending order
    - This allows us to match the smallest cookies to the least greedy children first

    &nbsp;

    **Step 2: Initialise two pointers and a counter**

    - `child`: Pointer to the current child (starts at `0`)
    - `cookie`: Pointer to the current cookie (starts at `0`)
    - `content_children`: Counter for successfully assigned cookies (starts at `0`)

    &nbsp;

    **Step 3: Iterate with two pointers**

    - While we have both children and cookies left to consider:
      - If the current cookie `s[cookie]` can satisfy the current child `g[child]`:
        - Assign it: increment `content_children` and move to the next child
      - Move to the next cookie regardless (each cookie can only be used once)

    &nbsp;

    **Step 4: Return the result**

    - Return `content_children` after exhausting cookies or children

    &nbsp;

    This greedy approach works because sorting ensures we always try the smallest available option first, and matching the least demanding child with the smallest sufficient cookie maximises our remaining options.

  common_pitfalls:
    - title: Not Sorting First
      description: |
        Without sorting, you might give a large cookie to a child who only needed a small one, wasting resources.

        For example, with `g = [1, 3]` and `s = [3, 1]`, processing left-to-right without sorting would give cookie size `3` to the child with greed `1`. Now the remaining cookie (size `1`) can't satisfy the child with greed `3`, resulting in only 1 content child instead of the optimal 2.

        Sorting both arrays ensures the smallest sufficient cookie always goes to the least greedy unsatisfied child.
      wrong_approach: "Iterate without sorting"
      correct_approach: "Sort both arrays first, then use two pointers"

    - title: Trying All Combinations
      description: |
        A brute force approach of trying all possible assignments is exponential in complexity. With `n` children and `m` cookies, there are potentially `m!` ways to distribute cookies.

        The greedy approach with sorting gives us O(n log n + m log m) time, which is optimal for this problem.
      wrong_approach: "Try all permutations of cookie assignments"
      correct_approach: "Greedy matching after sorting"

    - title: Forgetting Empty Cookie Array
      description: |
        The cookie array `s` can be empty (`0 <= s.length`). If there are no cookies, no children can be satisfied, and the answer is `0`.

        The two-pointer approach handles this naturally since the loop won't execute if `s` is empty.

  key_takeaways:
    - "**Greedy + Sorting pattern**: When matching resources to demands, sort both and greedily assign the smallest sufficient resource to the least demanding request"
    - "**Two pointers on sorted arrays**: A common technique for efficient matching and pairing problems"
    - "**Local optimality leads to global optimality**: Giving the smallest valid cookie to the least greedy child never reduces our total count"
    - "**Related problems**: This pattern applies to task scheduling, resource allocation, and matching problems like Maximum Matching of Players With Trainers (LC 2410)"

  time_complexity: "O(n log n + m log m). We sort both arrays (where `n = len(g)` and `m = len(s)`), then do a single linear pass through both."
  space_complexity: "O(1) if sorting in-place, or O(n + m) for the space used by the sorting algorithm depending on implementation."

solutions:
  - approach_name: Sort + Two Pointers (Greedy)
    is_optimal: true
    code: |
      def find_content_children(g: list[int], s: list[int]) -> int:
          # Sort both arrays to enable greedy matching
          g.sort()  # Children by greed (least greedy first)
          s.sort()  # Cookies by size (smallest first)

          child = 0  # Pointer to current child
          cookie = 0  # Pointer to current cookie
          content_children = 0

          # Try to match cookies to children
          while child < len(g) and cookie < len(s):
              # Can this cookie satisfy this child?
              if s[cookie] >= g[child]:
                  # Yes! Assign it and move to next child
                  content_children += 1
                  child += 1
              # Move to next cookie regardless
              # (if it couldn't satisfy current child, it won't satisfy greedier ones)
              cookie += 1

          return content_children
    explanation: |
      **Time Complexity:** O(n log n + m log m) — Sorting dominates, where n and m are the lengths of g and s.

      **Space Complexity:** O(1) auxiliary space (in-place sorting), or O(n + m) for Timsort's temporary storage.

      By sorting both arrays and using two pointers, we greedily match the smallest sufficient cookie to the least greedy child. Each cookie and child is considered at most once, making the matching phase O(n + m).

  - approach_name: Greedy with Binary Search
    is_optimal: false
    code: |
      import bisect

      def find_content_children(g: list[int], s: list[int]) -> int:
          # Sort both arrays
          g.sort()
          s.sort()

          content_children = 0
          available = list(s)  # Cookies still available

          # Process children from least to most greedy
          for greed in g:
              # Find smallest cookie that satisfies this child
              idx = bisect.bisect_left(available, greed)
              if idx < len(available):
                  # Found a suitable cookie, use it
                  content_children += 1
                  available.pop(idx)  # Remove used cookie

          return content_children
    explanation: |
      **Time Complexity:** O(n log n + m log m + n * m) — Sorting plus n binary searches with list removals.

      **Space Complexity:** O(m) — Copy of the cookie array.

      This approach uses binary search to find the smallest valid cookie for each child. While the binary search is O(log m), the `pop(idx)` operation is O(m), making this less efficient than the two-pointer approach. Included to show an alternative thought process.
