3753.范围内总波动值II

目标

给你两个整数 num1 和 num2,表示一个 闭 区间 [num1, num2]。

一个数字的 波动值 定义为该数字中 峰 和 谷 的总数:

  • 如果一个数位 严格大于 其两个相邻数位,则该数位为 峰。
  • 如果一个数位 严格小于 其两个相邻数位,则该数位为 谷。
  • 数字的第一个和最后一个数位 不能 是峰或谷。
  • 任何少于 3 位的数字,其波动值均为 0。

返回范围 [num1, num2] 内所有数字的波动值之和。

示例 1:

输入: num1 = 120, num2 = 130
输出: 3
解释:
在范围 [120, 130] 内:
120:中间数位 2 是峰,波动值 = 1。
121:中间数位 2 是峰,波动值 = 1。
130:中间数位 3 是峰,波动值 = 1。
范围内所有其他数字的波动值均为 0。
因此,总波动值为 1 + 1 + 1 = 3。

示例 2:

输入: num1 = 198, num2 = 202
输出: 3
解释:
在范围 [198, 202] 内:
198:中间数位 9 是峰,波动值 = 1。
201:中间数位 0 是谷,波动值 = 1。
202:中间数位 0 是谷,波动值 = 1。
范围内所有其他数字的波动值均为 0。
因此,总波动值为 1 + 1 + 1 = 3。

示例 3:

输入: num1 = 4848, num2 = 4848
输出: 2
解释:
数字 4848:第二个数位 8 是峰,第三个数位 4 是谷,波动值为 2。

说明:

  • 1 <= num1 <= num2 <= 10^15

思路

代码

性能

3751.范围内总波动值I

目标

给你两个整数 num1 和 num2,表示一个 闭 区间 [num1, num2]。

一个数字的 波动值 定义为该数字中 峰 和 谷 的总数:

  • 如果一个数位 严格大于 其两个相邻数位,则该数位为 峰。
  • 如果一个数位 严格小于 其两个相邻数位,则该数位为 谷。
  • 数字的第一个和最后一个数位 不能 是峰或谷。
  • 任何少于 3 位的数字,其波动值均为 0。

返回范围 [num1, num2] 内所有数字的波动值之和。

示例 1:

输入: num1 = 120, num2 = 130
输出: 3
解释:
在范围 [120, 130] 内:
120:中间数位 2 是峰,波动值 = 1。
121:中间数位 2 是峰,波动值 = 1。
130:中间数位 3 是峰,波动值 = 1。
范围内所有其他数字的波动值均为 0。
因此,总波动值为 1 + 1 + 1 = 3。

示例 2:

输入: num1 = 198, num2 = 202
输出: 3
解释:
在范围 [198, 202] 内:
198:中间数位 9 是峰,波动值 = 1。
201:中间数位 0 是谷,波动值 = 1。
202:中间数位 0 是谷,波动值 = 1。
范围内所有其他数字的波动值均为 0。
因此,总波动值为 1 + 1 + 1 = 3。

示例 3:

输入: num1 = 4848, num2 = 4848
输出:
解释:
数字 4848:第二个数位 8 是峰,第三个数位 4 是谷,波动值为 2。

说明:

  • 1 <= num1 <= num2 <= 10^5

思路

定义数字的波动值是数字中峰与谷的总数,峰指左右两侧的数字严格小于当前数字,谷指左右两侧的数字严格大于当前数字。求 [nums1, nums2] 中数字波动值的总和。

暴力计算每个数字的波动值。

代码


/**
 * @date 2026-06-04 9:04
 */
public class TotalWaviness3751 {

    public int totalWaviness(int num1, int num2) {
        int res = 0;
        for (int i = num1; i <= num2; i++) {
            String s = String.valueOf(i);
            for (int j = 1; j < s.length() - 1; j++) {
                if ((s.charAt(j) > s.charAt(j - 1) && s.charAt(j) > s.charAt(j + 1)) ||
                        ((s.charAt(j) < s.charAt(j - 1) && s.charAt(j) < s.charAt(j + 1)))) {
                    res++;
                }
            }
        }
        return res;
    }

}

性能

1871.跳跃游戏VII

目标

给你一个下标从 0 开始的二进制字符串 s 和两个整数 minJump 和 maxJump 。一开始,你在下标 0 处,且该位置的值一定为 '0' 。当同时满足如下条件时,你可以从下标 i 移动到下标 j 处:

  • i + minJump <= j <= min(i + maxJump, s.length - 1) 且
  • s[j] == '0'.

如果你可以到达 s 的下标 s.length - 1 处,请你返回 true ,否则返回 false 。

示例 1:

输入:s = "011010", minJump = 2, maxJump = 3
输出:true
解释:
第一步,从下标 0 移动到下标 3 。
第二步,从下标 3 移动到下标 5 。

示例 2:

输入:s = "01101110", minJump = 2, maxJump = 3
输出:false

说明:

  • 2 <= s.length <= 10^5
  • s[i] 要么是 '0' ,要么是 '1'
  • s[0] == '0'
  • 1 <= minJump <= maxJump < s.length

思路

有一个长度为 n 的二进制字符串 s,开始在位置 0 且该位置的值为 0,从该位置出发每次可以跳跃到 [i + minJump, min(i + maxJump, n - 1)] 中元素值为 0 的位置 j,判断能否到达 n - 1

定义 dp[i] 表示能否到达下标 i,如果 dp[i] = true,标记 [Math.max(j, i + minJump), Math.min(n - 1, i + maxJump)] 中元素值为 '0' 的下标,其中 j 表示之前可覆盖的最大下标 + 1。

代码


/**
 * @date 2026-05-25 8:50
 */
public class CanReach1871 {

    public boolean canReach(String s, int minJump, int maxJump) {
        int n = s.length();
        char[] chars = s.toCharArray();
        if (chars[n - 1] != '0') {
            return false;
        }
        boolean[] dp = new boolean[n];
        dp[0] = true;
        int j = 1;
        for (int i = 0; i < n; i++) {
            if (dp[i]) {
                for (j = Math.max(j, i + minJump); j <= Math.min(n - 1, i + maxJump); j++) {
                    dp[j] = chars[j] == '0';
                }
            }
        }
        return dp[n - 1];
    }

}

性能

1340.跳跃游戏V

目标

给你一个整数数组 arr 和一个整数 d 。每一步你可以从下标 i 跳到:

  • i + x ,其中 i + x < arr.length 且 0 < x <= d 。
  • i - x ,其中 i - x >= 0 且 0 < x <= d 。

除此以外,你从下标 i 跳到下标 j 需要满足:arr[i] > arr[j] 且 arr[i] > arr[k] ,其中下标 k 是所有 i 到 j 之间的数字(更正式的,min(i, j) < k < max(i, j))。

你可以选择数组的任意下标开始跳跃。请你返回你 最多 可以访问多少个下标。

请注意,任何时刻你都不能跳到数组的外面。

示例 1:

输入:arr = [6,4,14,6,8,13,9,7,10,6,12], d = 2
输出:4
解释:你可以从下标 10 出发,然后如上图依次经过 10 --> 8 --> 6 --> 7 。
注意,如果你从下标 6 开始,你只能跳到下标 7 处。你不能跳到下标 5 处因为 13 > 9 。你也不能跳到下标 4 处,因为下标 5 在下标 4 和 6 之间且 13 > 9 。
类似的,你不能从下标 3 处跳到下标 2 或者下标 1 处。

示例 2:

输入:arr = [3,3,3,3,3], d = 3
输出:1
解释:你可以从任意下标处开始且你永远无法跳到任何其他坐标。

示例 3:

输入:arr = [7,6,5,4,3,2,1], d = 1
输出:7
解释:从下标 0 处开始,你可以按照数值从大到小,访问所有的下标。

示例 4:

输入:arr = [7,1,7,1,7,1], d = 2
输出:2

示例 5:

输入:arr = [66], d = 1
输出:1

说明:

  • 1 <= arr.length <= 1000
  • 1 <= arr[i] <= 10^5
  • 1 <= d <= arr.length

思路

// todo

代码

性能

2770.达到末尾下标所需的最大跳跃次数

目标

给你一个下标从 0 开始、由 n 个整数组成的数组 nums 和一个整数 target 。

你的初始位置在下标 0 。在一步操作中,你可以从下标 i 跳跃到任意满足下述条件的下标 j :

  • 0 <= i < j < n
  • -target <= nums[j] - nums[i] <= target

返回到达下标 n - 1 处所需的 最大跳跃次数 。

如果无法到达下标 n - 1 ,返回 -1 。

示例 1:

输入:nums = [1,3,6,4,1,2], target = 2
输出:3
解释:要想以最大跳跃次数从下标 0 到下标 n - 1 ,可以按下述跳跃序列执行操作:
- 从下标 0 跳跃到下标 1 。 
- 从下标 1 跳跃到下标 3 。 
- 从下标 3 跳跃到下标 5 。 
可以证明,从 0 到 n - 1 的所有方案中,不存在比 3 步更长的跳跃序列。因此,答案是 3 。

示例 2:

输入:nums = [1,3,6,4,1,2], target = 3
输出:5
解释:要想以最大跳跃次数从下标 0 到下标 n - 1 ,可以按下述跳跃序列执行操作:
- 从下标 0 跳跃到下标 1 。 
- 从下标 1 跳跃到下标 2 。 
- 从下标 2 跳跃到下标 3 。 
- 从下标 3 跳跃到下标 4 。 
- 从下标 4 跳跃到下标 5 。 
可以证明,从 0 到 n - 1 的所有方案中,不存在比 5 步更长的跳跃序列。因此,答案是 5 。

示例 3:

输入:nums = [1,3,6,4,1,2], target = 0
输出:-1
解释:可以证明不存在从 0 到 n - 1 的跳跃序列。因此,答案是 -1 。

提示:

  • 2 <= nums.length == n <= 1000
  • -10^9 <= nums[i] <= 10^9
  • 0 <= target <= 2 * 10^9

思路

有一个数组 nums,对于小标 i 可以跳到 j > i && |nums[j] - nums[i]| <= target,返回从 0 跳到 n - 1 的最大跳跃次数。

定义 dp[i] 表示到达 i 的最大跳跃次数,dp[0] = 0,枚举 j ∈ [i + 1, n - 1] 如果 Math.abs(nums[j] - nums[i]) <= targetdp[j] = Math.max(dp[j], dp[i] + 1),注意:如果 dp[i] == 0, i > 0,说明无法从 0 到达当前下标,直接跳过。

代码


/**
 * @date 2026-05-11 10:07
 */
public class MaximumJumps2770 {

    public int maximumJumps(int[] nums, int target) {
        int n = nums.length;
        int[] dp = new int[n];
        for (int i = 0; i < n; i++) {
            if (i > 0 && dp[i] == 0) {
                continue;
            }
            for (int j = i + 1; j < n; j++) {
                if (Math.abs(nums[j] - nums[i]) <= target) {
                    dp[j] = Math.max(dp[j], dp[i] + 1);
                }
            }
        }
        return dp[n - 1] == 0 ? -1 : dp[n - 1];
    }

}

性能

时间复杂度 O(n^2),//todo 线段树维护最大值

3660.跳跃游戏IX

目标

给你一个整数数组 nums。

从任意下标 i 出发,你可以根据以下规则跳跃到另一个下标 j:

  • 仅当 nums[j] < nums[i] 时,才允许跳跃到下标 j,其中 j > i。
  • 仅当 nums[j] > nums[i] 时,才允许跳跃到下标 j,其中 j < i。

对于每个下标 i,找出从 i 出发且可以跳跃 任意 次,能够到达 nums 中的 最大值 是多少。

返回一个数组 ans,其中 ans[i] 是从下标 i 出发可以到达的最大值。

示例 1:

输入: nums = [2,1,3]
输出: [2,2,3]
解释:
对于 i = 0:没有跳跃方案可以获得更大的值。
对于 i = 1:跳到 j = 0,因为 nums[j] = 2 大于 nums[i]。
对于 i = 2:由于 nums[2] = 3 是 nums 中的最大值,没有跳跃方案可以获得更大的值。
因此,ans = [2, 2, 3]。

示例 2:

输入: nums = [2,3,1]
输出: [3,3,3]
解释:
对于 i = 0:向后跳到 j = 2,因为 nums[j] = 1 小于 nums[i] = 2,然后从 i = 2 跳到 j = 1,因为 nums[j] = 3 大于 nums[2]。
对于 i = 1:由于 nums[1] = 3 是 nums 中的最大值,没有跳跃方案可以获得更大的值。
对于 i = 2:跳到 j = 1,因为 nums[j] = 3 大于 nums[2] = 1。
因此,ans = [3, 3, 3]。

说明:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9

思路

有一个整数数组 nums,从任意下标 i 出发,可以向后跳到比 nums[i] 小的位置,向前跳到比 nums[i] 大的位置。返回从每一个下标 i 出发跳跃任意次能够到达的 nums 中的最大值。

定义 dp[i] 表示下标 i 所能到达的最大值,记录前缀最大值 preMax 与后缀最小值 suffixMin。如果 preMax[i + 1] <= suffixMin[i + 1]dp[i] = preMax[i + 1],否则 dp[i] = dp[i + 1]

// todo: 树状数组 线段树 单调栈解法

代码


/**
 * @date 2026-05-07 14:41
 */
public class MaxValue3660 {

    public int[] maxValue(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        int[] preMax = new int[n + 1];
        for (int i = 0; i < n; i++) {
            preMax[i + 1] = Math.max(preMax[i], nums[i]);
        }
        int suffixMin = nums[n - 1];
        dp[n - 1] = preMax[n];
        for (int i = n - 2; i >= 0; i--) {
            if (preMax[i + 1] <= suffixMin) {
                dp[i] = preMax[i + 1];
            } else {
                dp[i] = dp[i + 1];
            }
            suffixMin = Math.min(suffixMin, nums[i]);
        }
        return dp;
    }

}

性能

788.旋转数字

目标

我们称一个数 X 为好数, 如果它的每位数字逐个地被旋转 180 度后,我们仍可以得到一个有效的,且和 X 不同的数。要求每位数字都要被旋转。

如果一个数的每位数字被旋转以后仍然还是一个数字, 则这个数是有效的。0, 1, 和 8 被旋转后仍然是它们自己;2 和 5 可以互相旋转成对方(在这种情况下,它们以不同的方向旋转,换句话说,2 和 5 互为镜像);6 和 9 同理,除了这些以外其他的数字旋转以后都不再是有效的数字。

现在我们有一个正整数 N, 计算从 1 到 N 中有多少个数 X 是好数?

示例:

输入: 10
输出: 4
解释: 
在[1, 10]中有四个好数: 2, 5, 6, 9。
注意 1 和 10 不是好数, 因为他们在旋转之后不变。

说明:

  • N 的取值范围是 [1, 10000]。

思路

判断 1 ~ N 的数字中,不含 3 4 7,且一定要含 2 5 6 9 的数字个数。

// todo:数位 dp

代码


/**
 * @date 2026-05-06 16:45
 */
public class RotatedDigits788 {

    public int rotatedDigits(int n) {
        int res = 0;
        Set<Integer> set = new HashSet<>();
        set.add(2);
        set.add(5);
        set.add(6);
        set.add(9);
        for (int i = 1; i <= n; i++) {
            int num = i;
            boolean valid = false;
            while (num > 0) {
                int d = num % 10;
                if (d == 3 || d == 4 || d == 7) {
                    break;
                }
                if (set.contains(d)){
                    valid = true;
                }
                num /= 10;
            }
            if (num == 0 && valid) {
                res++;
            }
        }
        return res;
    }
}

性能

396.旋转函数

目标

给定一个长度为 n 的整数数组 nums 。

假设 arrk 是数组 nums 顺时针旋转 k 个位置后的数组,我们定义 nums 的 旋转函数 F 为:

  • F(k) = 0 * arrk[0] + 1 * arrk[1] + ... + (n - 1) * arrk[n - 1]

返回 F(0), F(1), ..., F(n-1)中的最大值 。

生成的测试用例让答案符合 32 位 整数。

示例 1:

输入: nums = [4,3,2,6]
输出: 26
解释:
F(0) = (0 * 4) + (1 * 3) + (2 * 2) + (3 * 6) = 0 + 3 + 4 + 18 = 25
F(1) = (0 * 6) + (1 * 4) + (2 * 3) + (3 * 2) = 0 + 4 + 6 + 6 = 16
F(2) = (0 * 2) + (1 * 6) + (2 * 4) + (3 * 3) = 0 + 6 + 8 + 9 = 23
F(3) = (0 * 3) + (1 * 2) + (2 * 6) + (3 * 4) = 0 + 2 + 12 + 12 = 26
所以 F(0), F(1), F(2), F(3) 中的最大值是 F(3) = 26 。

示例 2:

输入: nums = [100]
输出: 0

说明:

  • n == nums.length
  • 1 <= n <= 10^5
  • -100 <= nums[i] <= 100

思路

有一个循环数组 numsarr_k 表示将数组 nums 循环右移 k 之后的数组。定义 F(k) = 0 * arr_k[0] + 1 * arr_k[1] + …… + (n - 1) * arr_k[n - 1],返回 F(0)、F(1)、……、F(n - 1) 的最大值。

F(k) = Σ(i * arr_k[i]),观察发现 F(k + 1) - F(k) = sum - n * arr_k[n - 1],其中 sumnums 所有元素和。从 F(k) -> F(k + 1),除了最后一个元素,所有元素都增加了一个,最后一个元素减少了 n - 1 个。这里前面多加了最后一个元素,所以后面减去 n 个。arr_k 的最后一个元素为 nums[n - k]

代码


/**
 * @date 2026-05-09 13:43
 */
public class MaxRotateFunction396 {

    public int maxRotateFunction(int[] nums) {
        int n = nums.length;
        int f = 0;
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += nums[i];
            f += i * nums[i];
        }
        int res = f;
        for (int i = 1; i < n; i++) {
            f += sum - n * nums[n - i];
            res = Math.max(res, f);
        }
        return res;
    }
}

性能

时间复杂度为 O(n)。

3742.网格中得分最大的路径

目标

给你一个 m x n 的网格 grid,其中每个单元格包含以下值之一:0、1 或 2。另给你一个整数 k。

你从左上角 (0, 0) 出发,目标是到达右下角 (m - 1, n - 1),只能向 右 或 下 移动。

每个单元格根据其值对路径有以下贡献:

  • 值为 0 的单元格:分数增加 0,花费 0。
  • 值为 1 的单元格:分数增加 1,花费 1。
  • 值为 2 的单元格:分数增加 2,花费 1。

返回在总花费不超过 k 的情况下可以获得的 最大分数 ,如果不存在有效路径,则返回 -1。

注意: 如果到达最后一个单元格时总花费超过 k,则该路径无效。

示例 1:

输入: grid = [[0, 1],[2, 0]], k = 1
输出: 2
解释:
最佳路径为:
单元格  grid[i][j] 当前分数 累计分数 当前花费 累计花费
(0, 0)     0         0      0        0       0
(1, 0)     2         2      2        1       1
(1, 1)     0         0      2        0       1
因此,可获得的最大分数为 2。

示例 2:

输入: grid = [[0, 1],[1, 2]], k = 1
输出: -1
解释:
不存在在总花费不超过 k 的情况下到达单元格 (1, 1) 的路径,因此答案是 -1。

说明:

  • 1 <= m, n <= 200
  • 0 <= k <= 10^3
  • grid[0][0] == 0
  • 0 <= grid[i][j] <= 2

思路

有一个 m x n 网格 grid,其元素值可取 0、1、2,从左上角 (0, 0) 移动到 (m - 1, n - ),每次移动只能向右或向下,移动的代价为 grid[i][j] == 0 ? 0 : 1。求总代价不超过 k 的条件下,从左上移动到右下的最大得分,如果不存在有效路径返回 -1

定义 dp[i + 1][j + 1][c] 表示到达坐标 (i, j) 且代价不超过 c - 1 的最大得分。状态转移方程为 dp[i + 1][j + 1][c] = Math.max(dp[i][j + 1][c + cost], dp[i + 1][j][c + cost]) + grid[i][j],其中 cost = grid[i][j] == 0 ? 0 : -1

可以进行空间优化,直接将第一个维度去掉,内层倒序遍历即可。

代码


/**
 * @date 2026-04-30 8:57
 */
public class MaxPathScore3742 {

    public int maxPathScore_v2(int[][] grid, int k) {
        int n = grid[0].length;
        int[][] dp = new int[n + 1][k + 2];
        for (int[] row : dp) {
            Arrays.fill(row, Integer.MIN_VALUE);
        }
        Arrays.fill(dp[1], 1, k + 2, 0);
        for (int[] row : grid) {
            for (int j = 0; j < n; j++) {
                int cost = row[j] == 0 ? 0 : -1;
                for (int c = k + 1; c >=1; c--) {
                    dp[j + 1][c] = Math.max(dp[j + 1][c + cost], dp[j][c + cost]) + row[j];
                }
            }
        }
        return dp[n][k + 1] < 0 ? -1 : dp[n][k + 1];
    }

}

性能

3225.网格图操作后的最大分数

目标

给你一个大小为 n x n 的二维矩阵 grid ,一开始所有格子都是白色的。一次操作中,你可以选择任意下标为 (i, j) 的格子,并将第 j 列中从最上面到第 i 行所有格子改成黑色。

如果格子 (i, j) 为白色,且左边或者右边的格子至少一个格子为黑色,那么我们将 grid[i][j] 加到最后网格图的总分中去。

请你返回执行任意次操作以后,最终网格图的 最大 总分数。

示例 1:

输入:grid = [[0,0,0,0,0],[0,0,3,0,0],[0,1,0,0,0],[5,0,0,3,0],[0,0,0,0,2]]
输出:11
解释:
第一次操作中,我们将第 1 列中,最上面的格子到第 3 行的格子染成黑色。第二次操作中,我们将第 4 列中,最上面的格子到最后一行的格子染成黑色。最后网格图总分为 grid[3][0] + grid[1][2] + grid[3][3] 等于 11 。

示例 2:

输入:grid = [[10,9,0,0,15],[7,1,0,8,0],[5,20,0,11,0],[0,0,0,1,2],[8,12,1,10,3]]
输出:94
解释:
我们对第 1 ,2 ,3 列分别从上往下染黑色到第 1 ,4, 0 行。最后网格图总分为 grid[0][0] + grid[1][0] + grid[2][1] + grid[4][1] + grid[1][3] + grid[2][3] + grid[3][3] + grid[4][3] + grid[0][4] 等于 94 。

说明:

  • 1 <= n == grid.length <= 100
  • n == grid[i].length
  • 0 <= grid[i][j] <= 10^9

思路

代码

性能