title: Check If It Is a Good Array
slug: check-if-it-is-a-good-array
difficulty: hard
leetcode_id: 1250
leetcode_url: https://leetcode.com/problems/check-if-it-is-a-good-array/
categories:
  - arrays
  - math
patterns:
  - greedy

function_signature: "def is_good_array(nums: list[int]) -> bool:"

test_cases:
  visible:
    - input: { nums: [12, 5, 7, 23] }
      expected: true
    - input: { nums: [29, 6, 10] }
      expected: true
    - input: { nums: [3, 6] }
      expected: false
  hidden:
    - input: { nums: [1] }
      expected: true
    - input: { nums: [2, 4, 6, 8] }
      expected: false
    - input: { nums: [6, 9, 15, 21] }
      expected: false
    - input: { nums: [5, 7] }
      expected: true
    - input: { nums: [100, 99] }
      expected: true
    - input: { nums: [2, 3] }
      expected: true

description: |
  Given an array `nums` of positive integers. Your task is to select some subset of `nums`, multiply each element by an integer and add all these numbers.

  The array is said to be **good** if you can obtain a sum of `1` from the array by any possible subset and multiplicand.

  Return `True` if the array is **good** otherwise return `False`.

constraints: |
  - `1 <= nums.length <= 10^5`
  - `1 <= nums[i] <= 10^9`

examples:
  - input: "nums = [12,5,7,23]"
    output: "true"
    explanation: "Pick numbers 5 and 7. 5*3 + 7*(-2) = 1"
  - input: "nums = [29,6,10]"
    output: "true"
    explanation: "Pick numbers 29, 6 and 10. 29*1 + 6*(-3) + 10*(-1) = 1"
  - input: "nums = [3,6]"
    output: "false"
    explanation: "Both 3 and 6 are divisible by 3, so any linear combination will also be divisible by 3, never equal to 1."

explanation:
  intuition: |
    This problem might look intimidating at first — selecting subsets, multiplying by arbitrary integers, and summing to exactly `1`. But there's a beautiful mathematical theorem that makes this trivial once you recognise it.

    **Bézout's Identity** states that for any two integers `a` and `b`, there exist integers `x` and `y` such that `ax + by = gcd(a, b)`. This extends to any number of integers: given `a₁, a₂, ..., aₙ`, we can find integers `x₁, x₂, ..., xₙ` such that `a₁x₁ + a₂x₂ + ... + aₙxₙ = gcd(a₁, a₂, ..., aₙ)`.

    The key insight is that `1` can be expressed as such a linear combination **if and only if** the GCD of all numbers is `1`. Why? Because:
    - Any linear combination of the numbers must be divisible by their GCD
    - So if the GCD is greater than `1`, we can never reach `1`
    - And if the GCD equals `1`, Bézout's identity guarantees we *can* reach `1`

    Think of it like this: the GCD represents the "smallest building block" that all numbers share. If that building block is `1`, we can construct any integer (including `1`). If it's larger, we can only construct multiples of that larger value.

  approach: |
    With Bézout's Identity, the solution becomes remarkably simple:

    **Step 1: Initialise the running GCD**

    - `result`: Set to the first element of the array, or we can start with `0` (since `gcd(0, x) = x`)

    &nbsp;

    **Step 2: Iterate through the array**

    - For each number in `nums`, compute `gcd(result, nums[i])`
    - Update `result` with this new GCD
    - **Early termination**: If `result` becomes `1` at any point, we can immediately return `True` — the GCD can never increase, and `1` is the smallest positive integer

    &nbsp;

    **Step 3: Return the result**

    - If the final GCD equals `1`, return `True`
    - Otherwise, return `False`

    &nbsp;

    The early termination optimisation is valuable because once we find two coprime numbers (GCD = 1), no further numbers can change this.

  common_pitfalls:
    - title: Overthinking the Subset Selection
      description: |
        The problem statement mentions "select some subset" and "multiply each element by an integer." This naturally leads to thinking about combinatorial approaches — trying different subsets, solving systems of equations, or using dynamic programming.

        However, this is a red herring. The mathematical insight (Bézout's Identity) tells us that the *existence* of such integers is guaranteed when GCD = 1. We don't need to find the actual coefficients, just verify the GCD condition.
      wrong_approach: "Dynamic programming or subset enumeration"
      correct_approach: "Simply compute the GCD of all elements"

    - title: Missing the GCD Connection
      description: |
        If you don't recognise this as a number theory problem, you might try brute force approaches that will time out. With `n` up to `10^5` elements and values up to `10^9`, any exponential or polynomial approach in terms of values won't work.

        The GCD approach is O(n log(max_value)), which handles even the largest inputs efficiently.
      wrong_approach: "Brute force subset enumeration"
      correct_approach: "Recognise the Bézout's Identity pattern"

    - title: Not Using Early Termination
      description: |
        While not strictly necessary for correctness, failing to check if the GCD becomes `1` early means unnecessary computation. Once two numbers are coprime, the overall GCD is locked at `1`.

        For example, with `[1000000, 7, 3, 3, 3, ...]` (many 3s), we find GCD = 1 after just the first two elements. Without early termination, we'd wastefully compute GCDs with all remaining elements.
      wrong_approach: "Always compute GCD with all elements"
      correct_approach: "Return True immediately when GCD reaches 1"

  key_takeaways:
    - "**Bézout's Identity**: A linear combination of integers can equal `d` if and only if `d` is a multiple of their GCD — the key mathematical insight"
    - "**Recognise number theory patterns**: Problems involving linear combinations and integer solutions often reduce to GCD checks"
    - "**Early termination**: When computing running GCD, stop as soon as you reach `1` — it can never decrease"
    - "**Don't overcomplicate**: What looks like a hard combinatorial problem becomes trivial with the right mathematical perspective"

  time_complexity: "O(n log M). We iterate through `n` elements, and each GCD computation takes O(log M) where M is the maximum element value (using Euclidean algorithm)."
  space_complexity: "O(1). We only track a single running GCD value regardless of input size."

solutions:
  - approach_name: GCD with Early Termination
    is_optimal: true
    code: |
      from math import gcd
      from functools import reduce

      def is_good_array(nums: list[int]) -> bool:
          # Bézout's Identity: can reach 1 iff GCD of all elements is 1
          # Use reduce to compute GCD across the entire array
          return reduce(gcd, nums) == 1
    explanation: |
      **Time Complexity:** O(n log M) — n elements, each GCD takes O(log M) time.

      **Space Complexity:** O(1) — Only stores the running GCD.

      Python's `reduce` applies `gcd` cumulatively: `gcd(gcd(gcd(nums[0], nums[1]), nums[2]), ...)`. By Bézout's Identity, the array is "good" exactly when this overall GCD equals 1.

  - approach_name: Iterative GCD with Explicit Early Exit
    is_optimal: true
    code: |
      from math import gcd

      def is_good_array(nums: list[int]) -> bool:
          # Start with first element as our running GCD
          result = nums[0]

          for num in nums[1:]:
              # Update running GCD
              result = gcd(result, num)

              # Early termination: GCD of 1 can never decrease
              if result == 1:
                  return True

          # Check if final GCD is 1
          return result == 1
    explanation: |
      **Time Complexity:** O(n log M) — worst case iterates all elements, but often exits early.

      **Space Complexity:** O(1) — Single variable tracks the running GCD.

      This version explicitly shows the early termination optimisation. Once we find two coprime numbers (GCD = 1), no additional elements can change this fact. This is particularly effective when the array contains diverse numbers likely to be coprime.

  - approach_name: Mathematical Proof Verification
    is_optimal: false
    code: |
      from math import gcd

      def is_good_array(nums: list[int]) -> bool:
          # Compute GCD of all elements step by step
          overall_gcd = 0  # gcd(0, x) = x, so this works as identity

          for num in nums:
              overall_gcd = gcd(overall_gcd, num)

              # Optimisation: exit early if GCD reaches 1
              if overall_gcd == 1:
                  return True

          # By Bézout's Identity:
          # ax + by = gcd(a,b) for some integers x, y
          # Extended to n numbers: we can reach gcd(nums) via linear combination
          # Therefore, we can reach 1 iff gcd(all nums) == 1
          return overall_gcd == 1
    explanation: |
      **Time Complexity:** O(n log M) — Same as optimal, with more explicit comments.

      **Space Complexity:** O(1) — Only the running GCD variable.

      This version documents the mathematical reasoning inline. Starting with `0` works because `gcd(0, x) = x`, making it a valid identity element. The extended Euclidean algorithm guarantees that if GCD = 1, integer coefficients exist to form the linear combination.
