Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# 3753. Total Waviness of Numbers in Range II

[LeetCode Link](https://leetcode.com/problems/total-waviness-of-numbers-in-range-ii/)

Difficulty: Hard
Topics: Math, Dynamic Programming
Acceptance Rate: 42.9%

This one is genuinely hard — it asks you to aggregate a per-digit property over a
range that can hold up to 10^15 numbers, so brute force is hopeless. But the
structure is friendly once you spot it, and the technique (digit DP) is a
high-value pattern that shows up again and again. Take it in stages.

## Hints

### Hint 1

You cannot iterate over the range — it may contain ~10^15 numbers. Whenever a
problem says "sum/count some property over every integer in `[L, R]`," reach for
the **count-up-to-N** reduction: define `g(N)` as the answer over `[0, N]`, and
return `g(R) - g(L-1)`. That turns one range query into two prefix queries. Now
ask: what kind of DP lets me compute a digit-based statistic over `[0, N]`?

### Hint 2

The tool is **digit DP**: build numbers one digit at a time from the most
significant position, while carrying a "tight" flag that says whether the prefix
you've placed so far still hugs `N`'s prefix. The twist here is that you are not
counting numbers — you are summing a quantity. So each DP state should return
**two** things: how many numbers complete from here, and the **total waviness**
those completions accumulate.

### Hint 3

Waviness is purely *local*: whether a digit is a peak or valley depends only on
itself and its two immediate neighbors. So you only need to remember the **last
two real digits** as you move left to right. The key realization: a peak/valley
at some position can be *finalized the instant you place the digit to its right*.
If `prev2` (left), `prev1` (candidate middle), and the new digit `d` are all real
digits, then `prev1` contributes 1 to waviness exactly when
`prev1 > prev2 && prev1 > d` (peak) or `prev1 < prev2 && prev1 < d` (valley).
Add that contribution to every number flowing through the transition. Leading
zeros, and the "first/last digit can't be a peak" rule, fall out for free if you
track "no real digit yet" as a sentinel.

## Approach

**Reduction.** Define `g(N)` = sum of waviness over all integers in `[0, N]`. The
inclusive-range answer is `g(num2) - g(num1 - 1)`.

**State.** Process `N`'s digits most-significant first. At each position we carry:

- `pos` — the index we are about to fill.
- `tight` — whether the prefix so far equals `N`'s prefix (limits the next digit).
- `prev1` — the most recent **real** digit (the current peak/valley candidate).
- `prev2` — the real digit before that (the candidate's left neighbor).

We encode "no real digit placed yet" with the sentinel `10`. This single trick
handles leading zeros (a leading `0` keeps both `prev` values at the sentinel and
is not treated as a digit) **and** the boundary rules: the first real digit never
has a left neighbor, and the last digit never gets a right neighbor, so neither
can ever be scored as a peak/valley.

**Transition.** For each candidate digit `d` from `0` to the limit
(`N`'s digit at `pos` when tight, else `9`):

- If we are still in the leading-zero prefix (`prev1 == sentinel && d == 0`),
stay in the prefix; nothing is scored.
- Otherwise `d` is a real digit. If a real `prev2` exists, then `prev1` is now an
interior digit, and it contributes `1` iff it is a strict peak or strict valley
relative to `prev2` and `d`. Shift the window: `prev2 ← prev1`, `prev1 ← d`.

**Combining.** Each state returns `(cnt, sum)`. A child returns how many numbers
complete (`cnt`) and their accumulated waviness (`sum`). The parent folds in the
local contribution `c` of this transition as `sum += childSum + c * childCnt` —
because the contribution applies once to *every* number that passes through.

**Memoization.** Non-tight states `(pos, prev1, prev2)` repeat across many
prefixes, so memoize them. Tight states lie on a single path and aren't cached.

**Worked check.** For `4848`: digits `4,8,4,8`. When we place the third digit
`4`, the window is `prev2=4, prev1=8` → `8 > 4 && 8 > 4`, a peak (+1). When we
place the fourth digit `8`, the window is `prev2=8, prev1=4` → `4 < 8 && 4 < 8`,
a valley (+1). Total waviness `2`, matching the example.

## Complexity Analysis

Let `D ≈ 16` be the number of digits in `N` (since `N ≤ 10^15`).

Time Complexity: O(D · 11 · 11 · 10) per `g(N)` call — positions × `prev1`
states × `prev2` states × the 10 digit choices per transition. With the sentinel
that is a few thousand operations; effectively O(1) for this constraint. We call
`g` twice, so the whole solution is constant-bounded work.

Space Complexity: O(D · 11 · 11) for the memo table plus O(D) recursion depth.

## Edge Cases

- **Numbers with fewer than 3 digits.** They can have no interior digit, so their
waviness is 0. The sentinel-window logic never scores them because a real
`prev2` never materializes.
- **Leading zeros while building shorter numbers.** Building fixed-width prefixes
means a value like `7` is represented as `0…07`; the leading zeros must *not*
count as digits. The sentinel keeps them out of the waviness window.
- **First and last digit can never be peaks/valleys.** Handled structurally: the
first real digit is never a `prev1` with a real `prev2`, and the last digit is
never followed by another digit to finalize it.
- **`num1 - 1` underflow / `num1 = 1`.** `g(0)` must return 0, and `countWaviness`
guards negative inputs by returning 0 so `g(num1 - 1)` is always well defined.
- **Strictness.** Peaks/valleys require *strict* inequalities, so equal neighbors
(e.g. the middle `8` in `188`) are neither — easy to get wrong with `>=`/`<=`.
- **Magnitude of the result.** Up to ~10^15 numbers, each with up to ~13 interior
digits, so the sum can reach ~10^16; use 64-bit integers throughout.
85 changes: 85 additions & 0 deletions problems/3753-total-waviness-of-numbers-in-range-ii/problem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
number: "3753"
frontend_id: "3753"
title: "Total Waviness of Numbers in Range II"
slug: "total-waviness-of-numbers-in-range-ii"
difficulty: "Hard"
topics:
- "Math"
- "Dynamic Programming"
acceptance_rate: 4290.0
is_premium: false
created_at: "2026-06-05T04:54:58.265292+00:00"
fetched_at: "2026-06-05T04:54:58.265292+00:00"
link: "https://leetcode.com/problems/total-waviness-of-numbers-in-range-ii/"
date: "2026-06-05"
---

# 3753. Total Waviness of Numbers in Range II

You are given two integers `num1` and `num2` representing an **inclusive** range `[num1, num2]`.

The **waviness** of a number is defined as the total count of its **peaks** and **valleys** :

* A digit is a **peak** if it is **strictly greater** than both of its immediate neighbors.
* A digit is a **valley** if it is **strictly less** than both of its immediate neighbors.
* The first and last digits of a number **cannot** be peaks or valleys.
* Any number with fewer than 3 digits has a waviness of 0.

Return the total sum of waviness for all numbers in the range `[num1, num2]`.



**Example 1:**

**Input:** num1 = 120, num2 = 130

**Output:** 3

**Explanation:**

In the range `[120, 130]`:

* `120`: middle digit 2 is a peak, waviness = 1.
* `121`: middle digit 2 is a peak, waviness = 1.
* `130`: middle digit 3 is a peak, waviness = 1.
* All other numbers in the range have a waviness of 0.



Thus, total waviness is `1 + 1 + 1 = 3`.

**Example 2:**

**Input:** num1 = 198, num2 = 202

**Output:** 3

**Explanation:**

In the range `[198, 202]`:

* `198`: middle digit 9 is a peak, waviness = 1.
* `201`: middle digit 0 is a valley, waviness = 1.
* `202`: middle digit 0 is a valley, waviness = 1.
* All other numbers in the range have a waviness of 0.



Thus, total waviness is `1 + 1 + 1 = 3`.

**Example 3:**

**Input:** num1 = 4848, num2 = 4848

**Output:** 2

**Explanation:**

Number `4848`: the second digit 8 is a peak, and the third digit 4 is a valley, giving a waviness of 2.



**Constraints:**

* `1 <= num1 <= num2 <= 1015`​​​​​​​
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package main

// 3753. Total Waviness of Numbers in Range II
//
// Approach: Digit DP with a prefix-difference trick.
//
// Let g(N) = total waviness summed over every integer in [0, N]. Then the
// answer for an inclusive range is g(num2) - g(num1-1).
//
// We build numbers digit by digit from the most-significant end. A digit is a
// peak/valley only when it has two real neighbors, and a peak/valley can be
// finalized the moment we know the digit immediately to its right. So we carry
// the previous two *real* digits in the DP state:
//
// prev2 = the digit two positions back (the left neighbor of the candidate)
// prev1 = the digit one position back (the candidate middle digit)
//
// When we place a new digit d, if both previous real digits exist then prev1
// sits at an interior position and we can decide whether it is a peak
// (prev1 > prev2 && prev1 > d) or a valley (prev1 < prev2 && prev1 < d),
// contributing 1 to the waviness of every number that passes through this
// transition.
//
// Leading zeros are not real digits. We encode "no real digit placed yet" as
// the sentinel value 10, which also makes the first and last digits naturally
// ineligible to be peaks/valleys (the first digit is never the candidate with a
// left neighbor, and the last digit never gets a right neighbor).

func totalWaviness(num1 int64, num2 int64) int64 {
return countWaviness(num2) - countWaviness(num1-1)
}

// none marks "no real digit has been placed yet" (handles leading zeros).
const none = int8(10)

type stateKey struct {
pos int
prev1, prev2 int8
}

type dpResult struct {
cnt int64 // how many numbers complete from this state
sum int64 // total waviness contributed by those numbers' remaining digits
}

// countWaviness returns g(n): the sum of waviness over all integers in [0, n].
func countWaviness(n int64) int64 {
if n < 0 {
return 0
}

// Decompose n into its decimal digits, most-significant first.
var digits []int8
if n == 0 {
digits = []int8{0}
} else {
for n > 0 {
digits = append(digits, int8(n%10))
n /= 10
}
for i, j := 0, len(digits)-1; i < j; i, j = i+1, j-1 {
digits[i], digits[j] = digits[j], digits[i]
}
}
m := len(digits)

// Memo only covers the "not tight" states; tight states lie on a single
// path and are never revisited.
memo := make(map[stateKey]dpResult)

var dp func(pos int, tight bool, prev1, prev2 int8) dpResult
dp = func(pos int, tight bool, prev1, prev2 int8) dpResult {
if pos == m {
return dpResult{cnt: 1, sum: 0}
}
if !tight {
if v, ok := memo[stateKey{pos, prev1, prev2}]; ok {
return v
}
}

limit := int8(9)
if tight {
limit = digits[pos]
}

var res dpResult
for d := int8(0); d <= limit; d++ {
newTight := tight && d == limit

var np1, np2 int8
var contrib int64
if prev1 == none && d == 0 {
// Still inside the leading-zero prefix; no real digit yet.
np1, np2 = none, none
} else {
// d is a real digit. If two real digits precede it, prev1 is an
// interior digit and may be a peak or a valley.
if prev2 != none {
if (prev1 > prev2 && prev1 > d) || (prev1 < prev2 && prev1 < d) {
contrib = 1
}
}
np2 = prev1
np1 = d
}

child := dp(pos+1, newTight, np1, np2)
res.cnt += child.cnt
res.sum += child.sum + contrib*child.cnt
}

if !tight {
memo[stateKey{pos, prev1, prev2}] = res
}
return res
}

return dp(0, true, none, none).sum
}
Loading