3753. Total Waviness of Numbers in Range II

  • Digit DP
  • Dynamic Programming
  • Math
TIME
O(D 10 11 11 10)
SPACE
O(D 11 11)
problem on leetcode ↗source on github ↗

Intuition

The range can go up to 10^15, so scanning every number is impossible.

Instead, count the total waviness for all numbers from 0 to limit with digit DP. Then:

answer = count(num2) - count(num1 - 1)

When building a number from left to right, a digit can only be classified as a peak or valley after its right neighbor is known. So the DP keeps the last two significant digits. When a new digit is appended, the previous digit becomes an internal digit with both neighbors known.

State

dp(position, tight, started, previousPreviousDigit, previousDigit) returns:

  • how many numbers can be formed from this state
  • the total waviness contributed by all those numbers

previousPreviousDigit = -1 means only one significant digit has been placed so far.

Transition

For each possible next digit:

  1. If the number has not started and the digit is 0, keep skipping leading zeros.
  2. If this is the first significant digit, store it as previousDigit.
  3. If there is only one previous significant digit, store the pair.
  4. Otherwise, check whether previousDigit is a peak or valley using:
    previousPreviousDigit, previousDigit, currentDigit
    Add that contribution for every number formed by the suffix.

At the end of the digits, return one formed number with no additional contribution. The last digit is never counted because it never becomes internal.

Complexity

Let D be the number of digits in limit.

  • Time: O(D * 10 * 11 * 11 * 10)
  • Space: O(D * 11 * 11) for memoization

Since num2 <= 10^15, D is at most 16.

Edge Cases

  • Numbers with fewer than 3 digits -> no digit ever has both neighbors, so contribution is 0
  • Equal neighboring digits -> strict peak and valley comparisons fail
  • Leading zeros -> ignored until the first non-zero digit
  • Single-number range -> handled by prefix subtraction

Code

from functools import lru_cache
from typing import Tuple


class Solution:
    def totalWaviness(self, num1: int, num2: int) -> int:
        """
        3753. Total Waviness of Numbers in Range II
        Time: O(D * 10 * 11 * 11 * 10)
        Space: O(D * 11 * 11)
        """
        return self._count_up_to(num2) - self._count_up_to(num1 - 1)

    def _count_up_to(self, limit: int) -> int:
        if limit <= 0:
            return 0

        digits = list(map(int, str(limit)))

        @lru_cache(None)
        def dp(
            position: int,
            tight: bool,
            started: bool,
            previous_previous_digit: int,
            previous_digit: int,
        ) -> Tuple[int, int]:
            if position == len(digits):
                return 1, 0

            upper_bound = digits[position] if tight else 9
            total_numbers = 0
            total_waviness = 0

            for digit in range(upper_bound + 1):
                next_tight = tight and digit == upper_bound

                if not started and digit == 0:
                    count, waviness = dp(position + 1, next_tight, False, -1, -1)
                    total_numbers += count
                    total_waviness += waviness
                    continue

                if not started:
                    count, waviness = dp(position + 1, next_tight, True, -1, digit)
                    total_numbers += count
                    total_waviness += waviness
                    continue

                if previous_previous_digit == -1:
                    count, waviness = dp(
                        position + 1,
                        next_tight,
                        True,
                        previous_digit,
                        digit,
                    )
                    total_numbers += count
                    total_waviness += waviness
                    continue

                contribution = int(
                    previous_previous_digit < previous_digit > digit
                    or previous_previous_digit > previous_digit < digit
                )
                count, waviness = dp(position + 1, next_tight, True, previous_digit, digit)
                total_numbers += count
                total_waviness += waviness + contribution * count

            return total_numbers, total_waviness

        return dp(0, True, False, -1, -1)[1]