2026-04-07:范围内总波动值Ⅱ。用go语言,题意重新整理(纯文本描述): 给定两个整数 num1 和 num2,它们定义一个闭区间 [num1, num

0 阅读10分钟

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。

解题过程分步详解

一、先明确核心概念(必须先看懂)

  1. 波动值

    • 数字位数 < 3:波动值 = 0
    • 数字位数 ≥ 3:只看中间数位(排除第一位、最后一位)
    • 峰:中间数位 严格大于 左右两个邻居
    • 谷:中间数位 严格小于 左右两个邻居
    • 波动值 = 峰的数量 + 谷的数量
  2. 题目要求 给定区间 [num1, num2],求所有数字波动值的总和。 数字范围极大:1 ≤ num1 ≤ num2 ≤ 10^15(15位),不能暴力遍历每个数字


二、整体解题大框架(最顶层思路)

因为数字范围到 10¹⁵,暴力枚举会超时,所以用 数位动态规划(数位DP) 解决。 整体只有两步:

  1. 计算 f(X) = 从 0 到数字 X 的所有数字的总波动值
  2. 最终答案 = f(num2) - f(num1 - 1) (区间和 = 前缀和相减)

三、分步拆解:从 0 到 X 计算总波动值 f(X)

步骤1:特殊值快速判断

如果数字 X 小于 100(最多两位),那么所有数字波动值都是 0,直接返回 0。

步骤2:拆分数字的位数与结构

  1. 算出 X 是几位数(比如 130 是 3 位数)
  2. 把 X 拆成每一位的数字(1、3、0),方便逐位处理
  3. 准备好每一位的权重(百位:100,十位:10,个位:1)

步骤3:数位DP核心准备(记忆化搜索)

数位DP的作用:不枚举每个数字,而是按“位”枚举,统计所有合法数字的总波动值。 需要记录4个关键状态,避免重复计算:

  1. 当前处理到第几位(从左到右)
  2. 上一位的数字是什么(0~9)
  3. 上一段的大小关系(未知 / 上升 / 相等 / 下降)
  4. 是否紧贴数字上限(决定当前位能填 0~9 还是只能填到 X 的对应位)

同时用记忆数组缓存已经算过的状态,避免重复递归计算。


四、数位DP递归过程(逐位填数字,统计总波动值)

这是最核心的计算流程,我用纯文字描述:

1. 递归终止条件

所有位都填完时:

  • 返回两个值:总波动值=0有效数字个数=1(代表这是一个完整数字)

2. 记忆化剪枝

如果当前状态没有被上限限制,且之前已经计算过

  • 直接返回缓存的结果,不再重复递归

3. 确定当前位能填的数字范围

  • 如果紧贴上限:当前位最大只能填 X 对应位的数字
  • 如果不紧贴上限:当前位可以填 0~9 任意数字

4. 枚举当前位的所有可能数字 d

对每一个候选数字 d,执行以下判断:

  1. 更新状态

    • 新的上一位数字 = d
    • 新的大小关系:对比 d 和 上一位数字(小于/等于/大于)
    • 新的上限紧贴状态:只有之前紧贴且 d 等于上限数字时才保持紧贴
  2. 递归计算下一位 拿到下一位的两个结果:

    • 下一段的总波动值
    • 下一段的有效数字个数
  3. 累加当前位的贡献(最关键) 波动值只会在中间数位产生:

    • 当出现 峰/谷 时,波动值 +1
    • 规则:先上升后下降 = 峰先下降后上升 = 谷
    • 每出现一次峰/谷,就给所有后续数字都加上 1 点波动值
  4. 累计总数

    • 总波动值 += 下一段波动值 + 当前位新增的波动值
    • 总数字个数 += 下一段数字个数

5. 缓存结果(记忆化)

如果当前状态不受上限限制,把结果存入记忆数组,下次直接用。

6. 返回当前位的结果

返回:[当前总波动值,当前总数字个数]


五、完整流程总结(从输入到输出)

以输入 num1=120,num2=130 举例:

  1. 计算 f(130):0~130 所有数字总波动值
  2. 计算 f(119):0~119 所有数字总波动值
  3. 答案 = f(130) - f(119) = 3
  4. 对应数字: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)

总结

  1. 整体用数位DP + 前缀和解决超大数字范围问题,避免暴力枚举
  2. 核心是逐位填数、记录状态、统计峰谷贡献
  3. 时间复杂度:O(1) 常数级
  4. 空间复杂度:O(1) 常数级
  5. 完美支持 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;
}

在这里插入图片描述