DeepSeek    LeetCode 3753. 范围内总波动值 II Java实现

题目理解

波动值定义:

· 峰:数位 严格大于 其两个相邻数位
· 谷:数位 严格小于 其两个相邻数位
· 第一个和最后一个数位不能是峰或谷
· 少于3位的数字,波动值为0

示例:4848 中,第二个数位 8 是峰,第三个数位 4 是谷,波动值 = 2。

---

核心思路:前缀和 + 数位DP

直接枚举区间每个数字会超时(num 最大可达 10^15)。

核心技巧:

定义 f(n) = [0, n] 范围内所有数字的波动值之和
则区间 [num1, num2] 的答案 = f(num2) - f(num1 - 1)

这样只需实现计算 [0, n] 波动值之和的函数。

---

Java 实现(数位DP + 记忆化搜索)

```java
class Solution {
private int[] digits;
// dp[pos][pre2][pre][cnt][limit]
// pre2/pre: 10 表示"无前一位"(处理前导零)
private Long[][][][][] memo;

public long totalWaviness(long num1, long num2) {
return countUpTo(num2) - countUpTo(num1 - 1);
}

private long countUpTo(long n) {
if (n < 100) return 0; // 少于3位,波动值均为0
String s = String.valueOf(n);
digits = new int[s.length()];
for (int i = 0; i < s.length(); i++) {
digits[i] = s.charAt(i) - '0';
}
// 维度: pos, pre2(0-10), pre(0-10), cnt(0-20), limit(0/1)
memo = new Long[digits.length][11][11][20][2];
return dfs(0, 10, 10, 0, true, true);
}

/**
* @param pos 当前处理到的位置
* @param pre2 前两位的数字(10表示不存在)
* @param pre 前一位的数字(10表示不存在)
* @param cnt 已经累计的波动值
* @param limit 是否受上界 n 的限制
* @param lead 是否仍处于前导零状态
*/
private long dfs(int pos, int pre2, int pre, int cnt, boolean limit, boolean lead) {
if (pos == digits.length) {
return cnt;
}

if (!limit && memo[pos][pre2][pre][cnt][lead ? 1 : 0] != null) {
return memo[pos][pre2][pre][cnt][lead ? 1 : 0];
}

int maxDigit = limit ? digits[pos] : 9;
long res = 0;

for (int d = 0; d <= maxDigit; d++) {
boolean newLimit = limit && (d == maxDigit);
boolean newLead = lead && (d == 0);
int newCnt = cnt;

// 只有当前不是前导零,且前面已经有两个有效数字时,才判断 pre 是否为峰/谷
if (!newLead && pre2 != 10 && pre != 10) {
if ((pre > pre2 && pre > d) || (pre < pre2 && pre < d)) {
newCnt++;
}
}

int newPre2 = newLead ? 10 : pre;
int newPre = newLead ? 10 : d;

res += dfs(pos + 1, newPre2, newPre, newCnt, newLimit, newLead);
}

if (!limit) {
memo[pos][pre2][pre][cnt][lead ? 1 : 0] = res;
}
return res;
}
}
```

---

代码详解

1. 状态设计

· pre2:前两位数字(用于判断 pre 是否为峰/谷)
· pre:前一位数字
· 用 10 表示"不存在",因为数字范围是 0-9

2. 前导零处理

· lead = true 时,当前及之前所有位都是前导零(数字还未真正开始)
· 前导零不参与波动值计算

3. 判断峰/谷

当 pre2、pre、d 三个连续数位都存在时:

```java
if ((pre > pre2 && pre > d) || (pre < pre2 && pre < d)) {
newCnt++;
}
```

4. 记忆化条件

只有 !limit 时才能缓存,因为 limit=true 时可选数字范围因 n 而异。

---

复杂度分析

· 时间复杂度:O(len * 11 * 11 * 20 * 2 * 10) ≈ O(len * 24200),len ≤ 16,常数级
· 空间复杂度:O(len * 11 * 11 * 20 * 2)