title: Arithmetic Slices II - Subsequence
slug: arithmetic-slices-ii-subsequence
difficulty: hard
leetcode_id: 446
leetcode_url: https://leetcode.com/problems/arithmetic-slices-ii-subsequence/
categories:
  - arrays
  - dynamic-programming
  - hash-tables
patterns:
  - slug: dynamic-programming
    is_optimal: true

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

test_cases:
  visible:
    - input: { nums: [2, 4, 6, 8, 10] }
      expected: 7
    - input: { nums: [7, 7, 7, 7, 7] }
      expected: 16
  hidden:
    - input: { nums: [1] }
      expected: 0
    - input: { nums: [1, 2] }
      expected: 0
    - input: { nums: [1, 2, 3] }
      expected: 1
    - input: { nums: [1, 3, 5, 7] }
      expected: 3
    - input: { nums: [0, 0, 0, 0] }
      expected: 5
    - input: { nums: [1, 1, 1, 1, 1, 1] }
      expected: 42
    - input: { nums: [1, 2, 3, 4, 5, 6] }
      expected: 12

description: |
  Given an integer array `nums`, return *the number of all the **arithmetic subsequences** of* `nums`.

  A sequence of numbers is called arithmetic if it consists of **at least three elements** and if the difference between any two consecutive elements is the same.

  - For example, `[1, 3, 5, 7, 9]`, `[7, 7, 7, 7]`, and `[3, -1, -5, -9]` are arithmetic sequences.
  - For example, `[1, 1, 2, 5, 7]` is not an arithmetic sequence.

  A **subsequence** of an array is a sequence that can be formed by removing some elements (possibly none) of the array.

  - For example, `[2, 5, 10]` is a subsequence of `[1, 2, 1, 2, 4, 1, 5, 10]`.

  The test cases are generated so that the answer fits in a **32-bit** integer.

constraints: |
  - `1 <= nums.length <= 1000`
  - `-2^31 <= nums[i] <= 2^31 - 1`

examples:
  - input: "nums = [2,4,6,8,10]"
    output: "7"
    explanation: "All arithmetic subsequence slices are: [2,4,6], [4,6,8], [6,8,10], [2,4,6,8], [4,6,8,10], [2,4,6,8,10], [2,6,10]"
  - input: "nums = [7,7,7,7,7]"
    output: "16"
    explanation: "Any subsequence of this array is arithmetic since all elements are the same (difference = 0)."

explanation:
  intuition: |
    Imagine building arithmetic sequences piece by piece. The challenge is that a **subsequence** doesn't require consecutive elements — we can skip elements in the original array.

    The key insight is that we need to track **partial sequences** of length 2 or more. Why? Because a valid arithmetic subsequence needs at least 3 elements, but to extend any sequence, we need to know what 2-element "building blocks" exist and what their common difference is.

    Think of it like this: for each position `i` in the array, we maintain a record of "how many ways can I reach this position with a sequence ending here that has a specific difference `d`?" This includes both:
    - Pairs (length 2) that could become valid if extended
    - Longer sequences (length 3+) that are already valid

    When we process a new element `nums[i]`, we look back at every previous element `nums[j]`. The difference `d = nums[i] - nums[j]` tells us which sequences can be extended. If there were `k` sequences ending at `j` with difference `d`, we can extend all of them by adding `nums[i]`.

    The clever part: sequences of length 2 become length 3 (now valid and countable), and sequences already of length 3+ grow longer (still valid and countable). We count every extension of a sequence that was already length 2 or more.

  approach: |
    We use **Dynamic Programming with Hash Maps** to track subsequences by their common difference.

    **Step 1: Set up the data structure**

    - Create an array `dp` of hash maps, where `dp[i]` is a dictionary
    - `dp[i][d]` represents the count of subsequences (of length >= 2) ending at index `i` with common difference `d`
    - Initialise `result` to `0` to accumulate valid sequences (length >= 3)

    &nbsp;

    **Step 2: Iterate through all pairs**

    - For each index `i` from `0` to `n-1`:
      - For each previous index `j` from `0` to `i-1`:
        - Calculate the difference `d = nums[i] - nums[j]`

    &nbsp;

    **Step 3: Extend existing subsequences**

    - Look up how many subsequences end at `j` with difference `d` (call this `count_at_j`)
    - Add `count_at_j` to `result` — these are all the sequences that just became length 3+, or were already valid and got extended
    - Update `dp[i][d]` by adding `count_at_j + 1`:
      - The `+1` accounts for the new pair `(nums[j], nums[i])` with difference `d`
      - The `count_at_j` carries forward all extendable sequences from position `j`

    &nbsp;

    **Step 4: Return the result**

    - Return `result`, which counts every valid arithmetic subsequence (length >= 3)

    &nbsp;

    The magic is that we only add to `result` when extending sequences that already have at least 2 elements at position `j`. This ensures we only count sequences of length 3 or more.

  common_pitfalls:
    - title: Confusing Subsequences with Subarrays
      description: |
        A **subarray** requires consecutive elements, but a **subsequence** can skip elements. For example, in `[2, 4, 6, 8, 10]`, the subsequence `[2, 6, 10]` is valid (difference of 4) even though the elements aren't adjacent.

        This is why we need O(n^2) pairs — we must consider every possible pairing, not just adjacent elements.
      wrong_approach: "Only checking consecutive elements"
      correct_approach: "Check all pairs (i, j) where j < i"

    - title: Counting Pairs as Valid Sequences
      description: |
        A valid arithmetic sequence needs **at least 3 elements**. A common mistake is counting pairs (length 2) as valid.

        The solution handles this by only adding to `result` when we extend a sequence that already exists at `dp[j][d]`. A fresh pair `(nums[j], nums[i])` adds `1` to `dp[i][d]` but contributes `0` to `result` (since `dp[j][d]` was `0`).
      wrong_approach: "Counting every pair with a common difference"
      correct_approach: "Only count when extending existing sequences of length >= 2"

    - title: Integer Overflow in Difference Calculation
      description: |
        With constraints `-2^31 <= nums[i] <= 2^31 - 1`, the difference `nums[i] - nums[j]` can overflow a 32-bit integer.

        For example: `nums[i] = 2^31 - 1` and `nums[j] = -2^31` gives a difference of `2^32 - 1`, which exceeds 32-bit range.

        In Python this isn't an issue (arbitrary precision integers), but in languages like Java or C++, you must use `long` for the difference calculation.
      wrong_approach: "Using 32-bit integers for the difference"
      correct_approach: "Use 64-bit integers (long) for difference calculations"

    - title: Duplicate Elements Mishandling
      description: |
        When the array has duplicate values (e.g., `[7, 7, 7, 7, 7]`), many pairs share the same difference (0). Each pair can independently start or extend sequences.

        With 5 identical elements, there are C(5,3) + C(5,4) + C(5,5) = 10 + 5 + 1 = 16 valid subsequences of length 3+. The DP correctly accumulates these because it processes every pair.
      wrong_approach: "Skipping duplicate values"
      correct_approach: "Process every pair regardless of duplicates"

  key_takeaways:
    - "**DP with hash maps**: When the state space (possible differences) is large or sparse, use hash maps instead of fixed-size arrays"
    - "**Counting extensions**: Only count a sequence when it reaches the minimum valid length — track 'potential' sequences separately from 'valid' ones"
    - "**O(n^2) for subsequences**: Unlike subarray problems that can often be solved in O(n), subsequence problems typically require considering all pairs"
    - "**Foundation for sequence counting**: This pattern of tracking 'sequences ending at position i with property X' applies to many DP problems involving subsequences"

  time_complexity: "O(n^2). We iterate through all pairs `(i, j)` where `j < i`, and hash map operations are O(1) on average."
  space_complexity: "O(n^2). In the worst case, each position could have O(n) different differences stored in its hash map (e.g., when all elements are distinct)."

solutions:
  - approach_name: Dynamic Programming with Hash Maps
    is_optimal: true
    code: |
      def number_of_arithmetic_slices(nums: list[int]) -> int:
          n = len(nums)
          if n < 3:
              return 0

          # dp[i] maps difference -> count of subsequences ending at i
          dp = [dict() for _ in range(n)]
          result = 0

          for i in range(n):
              for j in range(i):
                  # Calculate the common difference
                  diff = nums[i] - nums[j]

                  # How many subsequences end at j with this difference?
                  count_at_j = dp[j].get(diff, 0)

                  # Add to result: these are valid sequences (length >= 3)
                  # or extensions of already valid sequences
                  result += count_at_j

                  # Update dp[i][diff]:
                  # - count_at_j sequences extended from j
                  # - +1 for the new pair (nums[j], nums[i])
                  dp[i][diff] = dp[i].get(diff, 0) + count_at_j + 1

          return result
    explanation: |
      **Time Complexity:** O(n^2) — We examine all pairs of indices.

      **Space Complexity:** O(n^2) — Each of the n hash maps can have up to O(n) entries.

      The key insight is separating "potential" sequences (length 2) from "valid" sequences (length 3+). By only adding `count_at_j` to the result (not the `+1` for new pairs), we ensure we only count sequences that have reached the minimum length of 3.

  - approach_name: Brute Force (Enumeration)
    is_optimal: false
    code: |
      def number_of_arithmetic_slices(nums: list[int]) -> int:
          from itertools import combinations

          n = len(nums)
          if n < 3:
              return 0

          count = 0

          # Check all subsequences of length 3 or more
          for length in range(3, n + 1):
              for indices in combinations(range(n), length):
                  # Extract the subsequence
                  subseq = [nums[i] for i in indices]
                  # Check if it's arithmetic
                  if is_arithmetic(subseq):
                      count += 1

          return count


      def is_arithmetic(seq: list[int]) -> bool:
          if len(seq) < 3:
              return False
          diff = seq[1] - seq[0]
          for i in range(2, len(seq)):
              if seq[i] - seq[i - 1] != diff:
                  return False
          return True
    explanation: |
      **Time Complexity:** O(2^n * n) — We enumerate all subsequences (2^n) and check each one (O(length)).

      **Space Complexity:** O(n) — For storing each subsequence during checking.

      This approach generates every possible subsequence of length 3 or more and checks if it's arithmetic. While correct, it's far too slow for the given constraints (n up to 1000). With n=1000, there are approximately 2^1000 subsequences — completely infeasible. This illustrates why the DP approach is necessary.
