diff --git a/problems/3753-total-waviness-of-numbers-in-range-ii/analysis_daily_20260605.md b/problems/3753-total-waviness-of-numbers-in-range-ii/analysis_daily_20260605.md new file mode 100644 index 0000000..730006b --- /dev/null +++ b/problems/3753-total-waviness-of-numbers-in-range-ii/analysis_daily_20260605.md @@ -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. diff --git a/problems/3753-total-waviness-of-numbers-in-range-ii/problem.md b/problems/3753-total-waviness-of-numbers-in-range-ii/problem.md new file mode 100644 index 0000000..b5674fc --- /dev/null +++ b/problems/3753-total-waviness-of-numbers-in-range-ii/problem.md @@ -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`​​​​​​​ diff --git a/problems/3753-total-waviness-of-numbers-in-range-ii/solution_daily_20260605.go b/problems/3753-total-waviness-of-numbers-in-range-ii/solution_daily_20260605.go new file mode 100644 index 0000000..083a15d --- /dev/null +++ b/problems/3753-total-waviness-of-numbers-in-range-ii/solution_daily_20260605.go @@ -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 +} diff --git a/problems/3753-total-waviness-of-numbers-in-range-ii/solution_daily_20260605_test.go b/problems/3753-total-waviness-of-numbers-in-range-ii/solution_daily_20260605_test.go new file mode 100644 index 0000000..35fed5e --- /dev/null +++ b/problems/3753-total-waviness-of-numbers-in-range-ii/solution_daily_20260605_test.go @@ -0,0 +1,95 @@ +package main + +import "testing" + +func TestTotalWaviness(t *testing.T) { + tests := []struct { + name string + num1 int64 + num2 int64 + expected int64 + }{ + {"example 1: [120,130] has three peaks", 120, 130, 3}, + {"example 2: [198,202] mixes a peak and two valleys", 198, 202, 3}, + {"example 3: 4848 has one peak and one valley", 4848, 4848, 2}, + {"edge case: single-digit range has no neighbors", 1, 9, 0}, + {"edge case: all two-digit numbers are too short", 10, 99, 0}, + {"edge case: single number 121 is exactly one peak", 121, 121, 1}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := totalWaviness(tt.num1, tt.num2); got != tt.expected { + t.Errorf("totalWaviness(%d, %d) = %d, want %d", tt.num1, tt.num2, got, tt.expected) + } + }) + } +} + +// wavinessBrute computes the waviness of a single number directly from its +// digits, used to cross-check the digit-DP solution. +func wavinessBrute(n int64) int64 { + if n < 0 { + return 0 + } + var digits []int + if n == 0 { + digits = []int{0} + } else { + for n > 0 { + digits = append(digits, int(n%10)) + n /= 10 + } + } + if len(digits) < 3 { + return 0 + } + // Peaks/valleys are symmetric under reversal, so scanning the (little-endian) + // interior positions directly yields the same count. + var w int64 + for i := 1; i < len(digits)-1; i++ { + left, mid, right := digits[i-1], digits[i], digits[i+1] + if (mid > left && mid > right) || (mid < left && mid < right) { + w++ + } + } + return w +} + +// sumWavinessBrute sums waviness over the inclusive range [a, b] by iterating. +func sumWavinessBrute(a, b int64) int64 { + var total int64 + for x := a; x <= b; x++ { + total += wavinessBrute(x) + } + return total +} + +// TestTotalWavinessAgainstBruteForce cross-checks the digit DP against a direct +// summation over many ranges, including boundaries that exercise leading zeros +// and carries (crossing 99 -> 100, 999 -> 1000, 99999 -> 100000). +func TestTotalWavinessAgainstBruteForce(t *testing.T) { + ranges := []struct { + a, b int64 + }{ + {0, 1}, + {1, 200}, + {120, 130}, + {198, 202}, + {95, 105}, + {990, 1010}, + {1, 5000}, + {4848, 4848}, + {99990, 100010}, + {12321, 12321}, + {1, 12345}, + {50000, 60000}, + } + + for _, r := range ranges { + want := sumWavinessBrute(r.a, r.b) + if got := totalWaviness(r.a, r.b); got != want { + t.Errorf("totalWaviness(%d, %d) = %d, brute force = %d", r.a, r.b, got, want) + } + } +}