2026-04-07:范围内总波动值Ⅱ。用go语言,题意重新整理(纯文本描述):
给定两个整数 num1 和 num2,它们定义一个闭区间 [num1, num2]。对区间内每个整数,都要计算它的“波动值”,然后把所有整数的波动值加起来。
“波动值”的计算规则如下:
1.把该整数按十进制拆成一串数位(从左到右)。
2.对每个“中间数位”(也就是除了最左和最右以外的数位),看它与左右相邻数位的大小关系:
-
如果某一数位严格大于它的左右相邻数位,那么这一个数位记为一个“峰”。
-
如果某一数位严格小于它的左右相邻数位,那么这一个数位记为一个“谷”。
3.最左边的数位和最右边的数位不能被认定为峰或谷(不参与峰谷计数)。
4.如果一个数字的位数少于 3 位,那么它的波动值规定为 0。
5.一个数字的波动值等于:其中所有“峰”的数量加上所有“谷”的数量。
最终要求:计算区间 [num1, num2] 内所有数字的波动值之和,并返回结果。
1 <= num1 <= num2 <= 1000000000000000。
输入: num1 = 120, num2 = 130。
输出: 3。
解释:
在范围 [120, 130] 内:
120:中间数位 2 是峰,波动值 = 1。
121:中间数位 2 是峰,波动值 = 1。
130:中间数位 3 是峰,波动值 = 1。
范围内所有其他数字的波动值均为 0。
因此,总波动值为 1 + 1 + 1 = 3。
题目来自力扣3753。
解题过程分步详解
一、先明确核心概念(必须先看懂)
-
波动值
- 数字位数 < 3:波动值 = 0
- 数字位数 ≥ 3:只看中间数位(排除第一位、最后一位)
- 峰:中间数位 严格大于 左右两个邻居
- 谷:中间数位 严格小于 左右两个邻居
- 波动值 = 峰的数量 + 谷的数量
-
题目要求 给定区间
[num1, num2],求所有数字波动值的总和。 数字范围极大:1 ≤ num1 ≤ num2 ≤ 10^15(15位),不能暴力遍历每个数字。
二、整体解题大框架(最顶层思路)
因为数字范围到 10¹⁵,暴力枚举会超时,所以用 数位动态规划(数位DP) 解决。 整体只有两步:
- 计算
f(X)= 从 0 到数字 X 的所有数字的总波动值 - 最终答案 =
f(num2) - f(num1 - 1)(区间和 = 前缀和相减)
三、分步拆解:从 0 到 X 计算总波动值 f(X)
步骤1:特殊值快速判断
如果数字 X 小于 100(最多两位),那么所有数字波动值都是 0,直接返回 0。
步骤2:拆分数字的位数与结构
- 算出 X 是几位数(比如 130 是 3 位数)
- 把 X 拆成每一位的数字(1、3、0),方便逐位处理
- 准备好每一位的权重(百位:100,十位:10,个位:1)
步骤3:数位DP核心准备(记忆化搜索)
数位DP的作用:不枚举每个数字,而是按“位”枚举,统计所有合法数字的总波动值。 需要记录4个关键状态,避免重复计算:
- 当前处理到第几位(从左到右)
- 上一位的数字是什么(0~9)
- 上一段的大小关系(未知 / 上升 / 相等 / 下降)
- 是否紧贴数字上限(决定当前位能填 0~9 还是只能填到 X 的对应位)
同时用记忆数组缓存已经算过的状态,避免重复递归计算。
四、数位DP递归过程(逐位填数字,统计总波动值)
这是最核心的计算流程,我用纯文字描述:
1. 递归终止条件
当所有位都填完时:
- 返回两个值:
总波动值=0、有效数字个数=1(代表这是一个完整数字)
2. 记忆化剪枝
如果当前状态没有被上限限制,且之前已经计算过:
- 直接返回缓存的结果,不再重复递归
3. 确定当前位能填的数字范围
- 如果紧贴上限:当前位最大只能填 X 对应位的数字
- 如果不紧贴上限:当前位可以填 0~9 任意数字
4. 枚举当前位的所有可能数字 d
对每一个候选数字 d,执行以下判断:
-
更新状态
- 新的上一位数字 = d
- 新的大小关系:对比 d 和 上一位数字(小于/等于/大于)
- 新的上限紧贴状态:只有之前紧贴且 d 等于上限数字时才保持紧贴
-
递归计算下一位 拿到下一位的两个结果:
- 下一段的总波动值
- 下一段的有效数字个数
-
累加当前位的贡献(最关键) 波动值只会在中间数位产生:
- 当出现 峰/谷 时,波动值 +1
- 规则:先上升后下降 = 峰,先下降后上升 = 谷
- 每出现一次峰/谷,就给所有后续数字都加上 1 点波动值
-
累计总数
- 总波动值 += 下一段波动值 + 当前位新增的波动值
- 总数字个数 += 下一段数字个数
5. 缓存结果(记忆化)
如果当前状态不受上限限制,把结果存入记忆数组,下次直接用。
6. 返回当前位的结果
返回:[当前总波动值,当前总数字个数]
五、完整流程总结(从输入到输出)
以输入 num1=120,num2=130 举例:
- 计算
f(130):0~130 所有数字总波动值 - 计算
f(119):0~119 所有数字总波动值 - 答案 = f(130) - f(119) = 3
- 对应数字:120(1)、121(1)、130(1),总和 3
六、时间复杂度 & 额外空间复杂度
1. 时间复杂度
O(位数 × 10 × 4 × 2)
- 位数:最多 15 位(10¹⁵)
- 10:上一位数字 0~9
- 4:大小关系(未知/小于/等于/大于)
- 2:紧贴上限状态(是/否)
总状态数极少:15 × 10 × 4 × 2 = 1200 种
每个状态只计算一次,时间复杂度是 常数级 O(1),极快。
2. 额外空间复杂度
O(位数 × 10 × 4 × 2)
- 只用到一个记忆化数组存储 DP 状态
- 数组大小固定:最多 1200 个元素
- 空间复杂度也是 常数级 O(1)
总结
- 整体用数位DP + 前缀和解决超大数字范围问题,避免暴力枚举
- 核心是逐位填数、记录状态、统计峰谷贡献
- 时间复杂度:O(1) 常数级
- 空间复杂度:O(1) 常数级
- 完美支持 1~10¹⁵ 的范围要求
Go完整代码如下:
package main
import (
"fmt"
)
const (
UNKNOWN = 0
LESS = 1
EQUAL = 2
GREATER = 3
)
func totalWaviness(num1 int64, num2 int64) int64 {
return totalWavinessWithBound(num2) - totalWavinessWithBound(num1-1)
}
func totalWavinessWithBound(n int64) int64 {
if n <= 100 {
return 0
}
m := getLength(n)
factor := int64(1)
for i := 1; i < m; i++ {
factor *= 10
}
// 创建memo数组: [m][10][4][2]
memo := make([][][][]int64, m)
for i := 0; i < m; i++ {
memo[i] = make([][][]int64, 10)
for j := 0; j < 10; j++ {
memo[i][j] = make([][]int64, 4)
for k := 0; k < 4; k++ {
memo[i][j][k] = []int64{-1, -1}
}
}
}
res := dp(memo, n, factor, 0, 0, UNKNOWN, true)
return res[0]
}
func getLength(n int64) int {
length := 0
for n > 0 {
n /= 10
length++
}
return length
}
func dp(memo [][][][]int64, n int64, factor int64, position int, prev int, comparison int, tight bool) []int64 {
if position == len(memo) {
return []int64{0, 1}
}
if !tight && memo[position][prev][comparison][0] >= 0 {
return []int64{memo[position][prev][comparison][0], memo[position][prev][comparison][1]}
}
var newWaviness int64 = 0
var newCount int64 = 0
digit := int(n / factor % 10)
maxDigit := 9
if tight {
maxDigit = digit
}
for d := 0; d <= maxDigit; d++ {
newPrev := d
newComparison := getNewComparison(d, prev, comparison)
newTight := tight && (d == digit)
var nextFactor int64 = 1
if factor > 1 {
nextFactor = factor / 10
}
next := dp(memo, n, nextFactor, position+1, newPrev, newComparison, newTight)
newWaviness += next[0] + int64(wavinessIncrease(comparison, newComparison))*next[1]
newCount += next[1]
}
if !tight {
memo[position][prev][comparison][0] = newWaviness
memo[position][prev][comparison][1] = newCount
}
return []int64{newWaviness, newCount}
}
func getNewComparison(curr int, prev int, comparison int) int {
if comparison == UNKNOWN && prev == 0 {
return UNKNOWN
}
if curr < prev {
return LESS
} else if curr == prev {
return EQUAL
} else {
return GREATER
}
}
func wavinessIncrease(comparison int, newComparison int) int {
if (comparison == LESS && newComparison == GREATER) || (comparison == GREATER && newComparison == LESS) {
return 1
}
return 0
}
func main() {
num1 := int64(120)
num2 := int64(130)
result := totalWaviness(num1, num2)
fmt.Printf("totalWaviness(%d, %d) = %d\n", num1, num2, result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
def total_waviness(num1: int, num2: int) -> int:
return total_waviness_with_bound(num2) - total_waviness_with_bound(num1 - 1)
def total_waviness_with_bound(n: int) -> int:
UNKNOWN, LESS, EQUAL, GREATER = 0, 1, 2, 3
if n <= 100:
return 0
m = get_length(n)
factor = 10 ** (m - 1)
# 创建memo字典,因为Python的多维列表初始化比较复杂
from functools import lru_cache
@lru_cache(maxsize=None)
def dp(position: int, prev: int, comparison: int, tight: bool) -> tuple:
if position == m:
return (0, 1)
new_waviness = 0
new_count = 0
digit = (n // factor) % 10 if position == 0 else (n // (10 ** (m - position - 1))) % 10
max_digit = digit if tight else 9
for d in range(max_digit + 1):
new_prev = d
new_comparison = get_new_comparison(d, prev, comparison, UNKNOWN)
new_tight = tight and (d == digit)
next_waviness, next_count = dp(position + 1, new_prev, new_comparison, new_tight)
new_waviness += next_waviness + waviness_increase(comparison, new_comparison, LESS, GREATER) * next_count
new_count += next_count
return (new_waviness, new_count)
result, _ = dp(0, 0, UNKNOWN, True)
return result
def get_length(n: int) -> int:
length = 0
while n > 0:
n //= 10
length += 1
return length if length > 0 else 1
def get_new_comparison(curr: int, prev: int, comparison: int, UNKNOWN: int) -> int:
LESS, EQUAL, GREATER = 1, 2, 3
if comparison == UNKNOWN and prev == 0:
return UNKNOWN
if curr < prev:
return LESS
elif curr == prev:
return EQUAL
else:
return GREATER
def waviness_increase(comparison: int, new_comparison: int, LESS: int, GREATER: int) -> int:
if (comparison == LESS and new_comparison == GREATER) or (comparison == GREATER and new_comparison == LESS):
return 1
return 0
def main():
num1 = 120
num2 = 130
result = total_waviness(num1, num2)
print(f"totalWaviness({num1}, {num2}) = {result}")
if __name__ == "__main__":
main()
C++完整代码如下:
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int UNKNOWN = 0;
const int LESS = 1;
const int EQUAL = 2;
const int GREATER = 3;
int getNewComparison(int curr, int prev, int comparison) {
if (comparison == UNKNOWN && prev == 0) {
return UNKNOWN;
}
if (curr < prev) {
return LESS;
} else if (curr == prev) {
return EQUAL;
} else {
return GREATER;
}
}
int wavinessIncrease(int comparison, int newComparison) {
if ((comparison == LESS && newComparison == GREATER) ||
(comparison == GREATER && newComparison == LESS)) {
return 1;
}
return 0;
}
int getLength(long long n) {
int length = 0;
while (n > 0) {
n /= 10;
length++;
}
return length;
}
// memo[position][prev][comparison][0] = waviness, [1] = count
vector<long long> dp(vector<vector<vector<vector<long long>>>>& memo,
long long n, long long factor,
int position, int prev, int comparison, bool tight) {
if (position == memo.size()) {
return {0, 1};
}
if (!tight && memo[position][prev][comparison][0] >= 0) {
return {memo[position][prev][comparison][0], memo[position][prev][comparison][1]};
}
long long newWaviness = 0;
long long newCount = 0;
int digit = (n / factor) % 10;
int maxDigit = tight ? digit : 9;
for (int d = 0; d <= maxDigit; d++) {
int newPrev = d;
int newComparison = getNewComparison(d, prev, comparison);
bool newTight = tight && (d == digit);
long long nextFactor = (factor > 1) ? factor / 10 : 1;
vector<long long> next = dp(memo, n, nextFactor, position + 1,
newPrev, newComparison, newTight);
newWaviness += next[0] + wavinessIncrease(comparison, newComparison) * next[1];
newCount += next[1];
}
if (!tight) {
memo[position][prev][comparison][0] = newWaviness;
memo[position][prev][comparison][1] = newCount;
}
return {newWaviness, newCount};
}
long long totalWavinessWithBound(long long n) {
if (n <= 100) {
return 0;
}
int m = getLength(n);
long long factor = 1;
for (int i = 1; i < m; i++) {
factor *= 10;
}
// 创建memo数组: [m][10][4][2]
vector<vector<vector<vector<long long>>>> memo(
m, vector<vector<vector<long long>>>(
10, vector<vector<long long>>(
4, vector<long long>(2, -1))));
vector<long long> res = dp(memo, n, factor, 0, 0, UNKNOWN, true);
return res[0];
}
long long totalWaviness(long long num1, long long num2) {
return totalWavinessWithBound(num2) - totalWavinessWithBound(num1 - 1);
}
int main() {
long long num1 = 120;
long long num2 = 130;
long long result = totalWaviness(num1, num2);
cout << "totalWaviness(" << num1 << ", " << num2 << ") = " << result << endl;
return 0;
}