LeetCode 每日一题笔记 日期:2026.06.04 题目:3751. 范围内总波动值 I

LeetCode 每日一题笔记

0. 前言

  • 日期:2026.06.04
  • 题目:3751. 范围内总波动值 I
  • 难度:中等
  • 标签:数位DP、枚举、数学

1. 题目理解

问题描述

给定闭区间 num1,num2\\mathit{num1},\\mathit{num2}num1,num2,数字波动值规则:

  1. 位数<3:波动值=0;
  2. 首尾数位不计入峰谷;
  3. 中间数位:严格大于左右邻居为 ,严格小于左右邻居为 ,峰+谷总数为本数波动值;
    求区间全部数字波动值累加和。

示例

输入:num1=121, num2=121

输出:1

解释:2>1且2>1,是峰,波动值=1。

2. 解题思路

核心观察

  • 朴素思路:逐个遍历区间数字,拆位统计单个数字峰谷数量,累加;适合数据范围小的场景;
  • 优化思路(数位DP+预处理):用f(x)代表0,x0,x0,x总波动和,答案=f(num2)−f(num1−1)f(\mathit{num2})-f(\mathit{num1}-1)f(num2)−f(num1−1),预计算数位统计数组,按数位分段快速求和,大数场景高效。

算法步骤

  1. 暴力枚举:遍历num1,num2\\mathit{num1},\\mathit{num2}num1,num2每一个数字,拆分字符/逐位取数,遍历中间位统计峰谷;
  2. 数位优化:预处理各长度数位前缀和数组,实现count(x)计算0~x总波动,差分得到区间答案。

3. 代码实现

java 复制代码
package lc3751;

class Solution {
    public int totalWaviness(int num1, int num2) {
        int res = 0;
        for (; num1 <= num2; num1++) {
            res += method1(num1);
        }
        return res;
    }
    public int method1(int num1) {
        if (num1 < 100) {
            return 0;
        }
        int res = 0;
        String s = String.valueOf(num1);
        int n = s.length();
        for (int i = 1; i <= n - 2; i++) {
            if (s.charAt(i) > s.charAt(i - 1) && s.charAt(i) > s.charAt(i + 1)) {
                res++;
            }
            if (s.charAt(i) < s.charAt(i - 1) && s.charAt(i) < s.charAt(i + 1)) {
                res++;
            }
        }
        return res;
    }
}

4. 代码优化说明

(原优化代码不变,添加逐行注释)

java 复制代码
class Solution {
    // 预处理数组:PEAK_VALLY_SUMS[位数][首位数字] 存对应前缀波动总和
    private static final long[][] PEAK_VALLY_SUMS = new long[16][11];
    // 预处理10的幂次,POW_OF_10S[i]=10^i
    private static final long[] POW_OF_10S = new long[16];

    static {
        long powOf10 = POW_OF_10S[0] = 1;
        // 预生成10的各次幂
        for(int i = 1; i < POW_OF_10S.length; i++) {
            POW_OF_10S[i] = powOf10 *= 10;
        }
        long prevSum = 0;
        // 预填充不同位数、不同首数字对应的波动前缀和
        for(int i = 2; i < PEAK_VALLY_SUMS.length; i++) {
            long[] row = PEAK_VALLY_SUMS[i];
            long sum = 0;
            for(int j = 0; j < 10; j++) {
                row[j + 1] = sum += prevSum + (45 + 9 * j - j * j) * POW_OF_10S[i - 2];
            }
            prevSum = sum;
        }
    }

    // 答案 = f(num2)-f(num1-1),差分求区间和
    public int totalWaviness(int num1, int num2) {
        return totalWaviness0(num2) - totalWaviness0(num1 - 1);
    }

    // 计算 [0,num] 所有数字波动总和
    int totalWaviness0(int num) {
        String str = Integer.toString(num);
        int len = str.length();
        // 小于3位全部无波动
        if(len <= 2) {
            return 0;
        }
        int digit = str.charAt(0) - '0';
        // 先累加同位数、首数字小于当前首位的全部数字波动和
        long sum = PEAK_VALLY_SUMS[len - 1][digit] - 5 * (POW_OF_10S[len - 2] - 1);
        int digit1 = -1, digit2;
        int prefixCount = 0;
        // 从次高位向后逐位遍历,统计固定前缀下合法后缀贡献
        for(int i = len - 2; i >= 0; i--) {
            digit2 = digit1;
            digit1 = digit;
            digit = str.charAt(len - 1 - i) - '0';
            if(i > 0) {
                // 累加剩余低位任意取值的预计算波动和
                sum += PEAK_VALLY_SUMS[i][digit];
                // 统计合法前后数位配对数量
                int pairCount;
                if(digit <= digit1) {
                    pairCount = (19 - digit) * digit >> 1;
                } else {
                    pairCount = (19 - digit1) * digit1 + (digit1 + digit) * (digit - digit1 - 1) >> 1;
                }
                sum += POW_OF_10S[i - 1] * pairCount;
            }
            // 存在前前位,判断当前三元组是否构成峰谷,累加对应低位全排列贡献
            if(digit2 >= 0) {
                if (digit2 > digit1) {
                    if (digit > digit1 + 1) {
                        sum += (digit - digit1 - 1) * POW_OF_10S[i];
                    }
                } else if (digit2 < digit1) {
                    sum += Math.min(digit, digit1) * POW_OF_10S[i];
                }
                sum += POW_OF_10S[i] * prefixCount * digit;
                // 当前中间位形成峰/谷,后续所有后缀数字都要多记1点波动
                if(digit > digit1 && digit1 < digit2 || digit < digit1 && digit1 > digit2) {
                    prefixCount++;
                }
            }
        }
        // 补全当前完整数字自身的峰谷计数
        sum += prefixCount;
        return (int)sum;
    }
}

5. 复杂度分析

  • 暴力枚举版
    时间:O((R−L+1)⋅D)O((R-L+1)\cdot D)O((R−L+1)⋅D),DDD 为数字平均位数;区间极大时会超时;
    空间:O(1)O(1)O(1),仅临时变量。
  • 数位预处理优化版
    时间:O(log⁡N)O(\log N)O(logN),预处理仅执行一次,单次查询按数字位数遍历;
    空间:O(1)O(1)O(1),预处理数组长度固定为常数。

6. 总结

  • 暴力思路:逻辑直观,适合小数范围,直接逐位判定峰谷;
  • 优化思路:前缀差分+数位预处理 ,f(r)−f(l−1)f(r)-f(l-1)f(r)−f(l−1) 求区间和,预处理预存各数位组合贡献,将复杂度从线性枚举压缩至数位对数级别;
  • 核心:单个峰谷只由连续三位决定,预处理批量统计同前缀下所有后缀的波动总和。
相关推荐
数智工坊1 小时前
周志华《Machine Learning》学习笔记--第七章--贝叶斯分类器
人工智能·笔记·神经网络·学习·机器学习
lightqjx1 小时前
【算法】数据结构_单调栈
数据结构·算法·单调栈
问心无愧05131 小时前
ctf show web入门99
android·前端·笔记
Promise微笑1 小时前
洞察无形:红外热像仪应用场景与高性价比之选
人工智能·物联网·算法
8Qi81 小时前
LeetCode 746:使用最小花费爬楼梯 —— 题解笔记
java·笔记·算法·leetcode·动态规划
pipo2 小时前
没雷达也能调 Nav2?我开源了一套仿真到实机复用的 ROS 2 3D LiDAR 导航工作空间
算法
二哈赛车手2 小时前
新人笔记---继图片搜索功能后续以及AI网络搜索功能一些经验与踩坑点,吐槽一下自己在做这方面的崩溃瞬间
java·网络·人工智能·spring boot·笔记·spring
计算机安禾2 小时前
【算法分析与设计】第44篇:随机化复杂度类:RP、BPP与去随机化猜想
java·数据结构·数据库·算法·机器学习
计算机安禾2 小时前
【算法分析与设计】第45篇:交互式证明系统与零知识证明
算法·区块链·零知识证明