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 线段树维护最大值

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;
    }
}

性能

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];
    }

}

性能

1320.二指输入的的最小距离

目标

二指输入法定制键盘在 X-Y 平面上的布局如上图所示,其中每个大写英文字母都位于某个坐标处。

  • 例如字母 A 位于坐标 (0,0),字母 B 位于坐标 (0,1),字母 P 位于坐标 (2,3) 且字母 Z 位于坐标 (4,1)。

给你一个待输入字符串 word,请你计算并返回在仅使用两根手指的情况下,键入该字符串需要的最小移动总距离。

坐标 (x1,y1) 和 (x2,y2) 之间的 距离 是 |x1 - x2| + |y1 - y2|。

注意,两根手指的起始位置是零代价的,不计入移动总距离。你的两根手指的起始位置也不必从首字母或者前两个字母开始。

示例 1:

输入:word = "CAKE"
输出:3
解释: 
使用两根手指输入 "CAKE" 的最佳方案之一是: 
手指 1 在字母 'C' 上 -> 移动距离 = 0 
手指 1 在字母 'A' 上 -> 移动距离 = 从字母 'C' 到字母 'A' 的距离 = 2 
手指 2 在字母 'K' 上 -> 移动距离 = 0 
手指 2 在字母 'E' 上 -> 移动距离 = 从字母 'K' 到字母 'E' 的距离  = 1 
总距离 = 3

示例 2:

输入:word = "HAPPY"
输出:6
解释: 
使用两根手指输入 "HAPPY" 的最佳方案之一是:
手指 1 在字母 'H' 上 -> 移动距离 = 0
手指 1 在字母 'A' 上 -> 移动距离 = 从字母 'H' 到字母 'A' 的距离 = 2
手指 2 在字母 'P' 上 -> 移动距离 = 0
手指 2 在字母 'P' 上 -> 移动距离 = 从字母 'P' 到字母 'P' 的距离 = 0
手指 1 在字母 'Y' 上 -> 移动距离 = 从字母 'A' 到字母 'Y' 的距离 = 4
总距离 = 6

说明:

  • 2 <= word.length <= 300
  • 每个 word[i] 都是一个大写英文字母。

提示:

  • dp[i][j][k]: smallest movements when you have one finger on i-th char and the other one on j-th char already having written k first characters from word.

思路

使用两个手指录入 word 字符串,求移动距离的最小值。坐标 (x1, y1)(x2, y2) 之间的 距离 是 |x1 - x2| + |y1 - y2|。字母的坐标见第一张图,起始位置的代价是 0

定义 dp[i][c] 表示已录入 word[0 ~ i] 且另一个手指在字母 c 的最小移动距离。

假设当前字母编号 cur = word[i] - 'A', 前一个位置的字母编号 prev = word[i - 1] - 'A',有 dp[i][c] = Math.min(dp[i - 1][c] + dis[prev][cur], dp[i - 1][cur] + dis[prev][c])

已录入 word[0 ~ i] 两个手指的所在的字母一个是 cur,一个是 c,可以从两个状态转移过来:

  • 一手指在字母 c 不动,一根手指从 prev 移动到 cur
  • 一手指在字母 cur 不动,一根手指从 prev 移动到 c

代码


/**
 * @date 2026-04-13 10:24
 */
public class MinimumDistance1320 {

    public int minimumDistance(String word) {
        int n = word.length();
        int[][] dp = new int[n][26];
        for (int i = 1; i < n; i++) {
            int prev = word.charAt(i - 1) - 'A';
            int cur = word.charAt(i) - 'A';
            for (int c = 0; c < 26; c++) {
                dp[i][c] = Math.min(dp[i - 1][c] + dis[prev][cur], dp[i - 1][cur] + dis[prev][c]);
            }
        }
        int res = Integer.MAX_VALUE;
        for (int d : dp[n - 1]) {
            res = Math.min(res, d);
        }
        return res;
    }

}

性能

3003.执行操作后的最大分割数量

目标

给你一个下标从 0 开始的字符串 s 和一个整数 k。

你需要执行以下分割操作,直到字符串 s 变为 空:

  • 选择 s 的最长 前缀,该前缀最多包含 k 个 不同 字符。
  • 删除 这个前缀,并将分割数量加一。如果有剩余字符,它们在 s 中保持原来的顺序。

执行操作之 前 ,你可以将 s 中 至多一处 下标的对应字符更改为另一个小写英文字母。

在最优选择情形下改变至多一处下标对应字符后,用整数表示并返回操作结束时得到的 最大 分割数量。

示例 1:

输入:s = "accca", k = 2
输出:3
解释:
最好的方式是把 s[2] 变为除了 a 和 c 之外的东西,比如 b。然后它变成了 "acbca"。
然后我们执行以下操作:
1. 最多包含 2 个不同字符的最长前缀是 "ac",我们删除它然后 s 变为 "bca"。
2. 现在最多包含 2 个不同字符的最长前缀是 "bc",所以我们删除它然后 s 变为 "a"。
3. 最后,我们删除 "a" 并且 s 变成空串,所以该过程结束。
进行操作时,字符串被分成 3 个部分,所以答案是 3。

示例 2:

输入:s = "aabaab", k = 3
输出:1
解释:
一开始 s 包含 2 个不同的字符,所以无论我们改变哪个, 它最多包含 3 个不同字符,因此最多包含 3 个不同字符的最长前缀始终是所有字符,因此答案是 1。

示例 3:

输入:s = "xxyz", k = 1
输出:4
解释:
最好的方式是将 s[0] 或 s[1] 变为 s 中字符以外的东西,例如将 s[0] 变为 w。
然后 s 变为 "wxyz",包含 4 个不同的字符,所以当 k 为 1,它将分为 4 个部分。

说明:

  • 1 <= s.length <= 10^4
  • s 只包含小写英文字母。
  • 1 <= k <= 26

思路

有一个字符串 s 和一个整数 k,允许至多将 s 中的任意一个字符替换为其它小写英文字母,然后循环执行以下操作:删除字符串 s 的 最长 前缀,要求前缀中 最多 包含 k 个不同字符。求最大的删除次数。

// todo

代码

性能

3186.施咒的最大总伤害

目标

一个魔法师有许多不同的咒语。

给你一个数组 power ,其中每个元素表示一个咒语的伤害值,可能会有多个咒语有相同的伤害值。

已知魔法师使用伤害值为 power[i] 的咒语时,他们就 不能 使用伤害为 power[i] - 2 ,power[i] - 1 ,power[i] + 1 或者 power[i] + 2 的咒语。

每个咒语最多只能被使用 一次 。

请你返回这个魔法师可以达到的伤害值之和的 最大值 。

示例 1:

输入:power = [1,1,3,4]
输出:6
解释:
可以使用咒语 0,1,3,伤害值分别为 1,1,4,总伤害值为 6 。

示例 2:

输入:power = [7,1,6,6]
输出:13
解释:
可以使用咒语 1,2,3,伤害值分别为 1,6,6,总伤害值为 13 。

说明:

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

思路

有一个数组 power 表示咒语的伤害值,魔法师使用其中任意咒语后,就无法再使用伤害值 +1 +2 -1 -2 的咒语,每个咒语只能使用一次,求伤害值的最大值。

使用哈希表记录伤害值相同的和,创建新数组(不包含重复伤害值),根据伤害值排序。如果不选当前伤害值,状态可以从后面转移而来,如果选则从后面大于当前伤害值 + 2 的转移而来。

代码


/**
 * @date 2025-10-11 8:57
 */
public class MaximumTotalDamage3186 {

    public long maximumTotalDamage(int[] power) {
        Map<Integer, Long> sum = new HashMap<>();
        for (int i : power) {
            sum.merge(i, (long) i, Long::sum);
        }
        int n = sum.size();
        int[] powers = new int[n];
        int i = 0;
        for (Integer damage : sum.keySet()) {
            powers[i++] = damage;
        }
        Arrays.sort(powers);
        long[] mem = new long[n + 1];
        mem[n - 1] = sum.get(powers[n - 1]);
        for (int j = n - 2; j >= 0; j--) {
            int k = j;
            while (k < n && powers[k] - powers[j] <= 2) {
                k++;
            }
            mem[j] = Math.max(mem[j + 1], mem[k] + sum.get(powers[j]));
        }
        return mem[0];
    }

}

性能

3459.最长V形对角线段的长度

目标

给你一个大小为 n x m 的二维整数矩阵 grid,其中每个元素的值为 0、1 或 2。

V 形对角线段 定义如下:

  • 线段从 1 开始。
  • 后续元素按照以下无限序列的模式排列:2, 0, 2, 0, ...。
  • 该线段:
    • 起始于某个对角方向(左上到右下、右下到左上、右上到左下或左下到右上)。
    • 沿着相同的对角方向继续,保持 序列模式 。
    • 在保持 序列模式 的前提下,最多允许 一次顺时针 90 度转向 另一个对角方向。

返回最长的 V 形对角线段 的 长度 。如果不存在有效的线段,则返回 0。

示例 1:

输入: grid = [[2,2,1,2,2],[2,0,2,2,0],[2,0,1,1,0],[1,0,2,2,2],[2,0,0,2,2]]
输出: 5
解释:
最长的 V 形对角线段长度为 5,路径如下:(0,2) → (1,3) → (2,4),在 (2,4) 处进行 顺时针 90 度转向 ,继续路径为 (3,3) → (4,2)。

示例 2:

输入: grid = [[2,2,2,2,2],[2,0,2,2,0],[2,0,1,1,0],[1,0,2,2,2],[2,0,0,2,2]]
输出: 4
解释:
最长的 V 形对角线段长度为 4,路径如下:(2,3) → (3,2),在 (3,2) 处进行 顺时针 90 度转向 ,继续路径为 (2,1) → (1,0)。

示例 3:

输入: grid = [[1,2,2,2,2],[2,2,2,2,0],[2,0,0,0,0],[0,0,2,2,2],[2,0,0,2,0]]
输出: 5
解释:
最长的 V 形对角线段长度为 5,路径如下:(0,0) → (1,1) → (2,2) → (3,3) → (4,4)。

示例 4:

输入: grid = [[1]]
输出: 1
解释:
最长的 V 形对角线段长度为 1,路径如下:(0,0)。

说明:

  • n == grid.length
  • m == grid[i].length
  • 1 <= n, m <= 500
  • grid[i][j] 的值为 0、1 或 2。

思路

代码

性能

2843.统计对称整数的数目

目标

给你两个正整数 low 和 high 。

对于一个由 2 * n 位数字组成的整数 x ,如果其前 n 位数字之和与后 n 位数字之和相等,则认为这个数字是一个对称整数。

返回在 [low, high] 范围内的 对称整数的数目 。

示例 1:

输入:low = 1, high = 100
输出:9
解释:在 1 到 100 范围内共有 9 个对称整数:11、22、33、44、55、66、77、88 和 99 。

示例 2:

输入:low = 1200, high = 1230
输出:4
解释:在 1200 到 1230 范围内共有 4 个对称整数:1203、1212、1221 和 1230 。

说明:

  • 1 <= low <= high <= 10^4

思路

计算给定区间内的对称整数数目,对称整数的长度为偶数,且左边数字之和等于右边数字之和。

数据范围小可以直接暴力枚举。

代码

class Solution {
    public int countSymmetricIntegers(int low, int high) {
        int res = 0;
        for (int i = low; i <= high; i++) {
            String num = String.valueOf(i);
            int r = num.length();
            if (r % 2 == 1) {
                continue;
            }
            r--;
            int l = 0;
            int diff = 0;
            while (l < r) {
                diff += num.charAt(l++) - num.charAt(r--);
            }
            res += diff != 0 ? 0 : 1;
        }
        return res;
    }
}

性能

2999.统计强大整数的数目

目标

给你三个整数 start ,finish 和 limit 。同时给你一个下标从 0 开始的字符串 s ,表示一个 正 整数。

如果一个 正 整数 x 末尾部分是 s (换句话说,s 是 x 的 后缀),且 x 中的每个数位至多是 limit ,那么我们称 x 是 强大的 。

请你返回区间 [start..finish] 内强大整数的 总数目 。

如果一个字符串 x 是 y 中某个下标开始(包括 0 ),到下标为 y.length - 1 结束的子字符串,那么我们称 x 是 y 的一个后缀。比方说,25 是 5125 的一个后缀,但不是 512 的后缀。

示例 1:

输入:start = 1, finish = 6000, limit = 4, s = "124"
输出:5
解释:区间 [1..6000] 内的强大数字为 124 ,1124 ,2124 ,3124 和 4124 。这些整数的各个数位都 <= 4 且 "124" 是它们的后缀。注意 5124 不是强大整数,因为第一个数位 5 大于 4 。
这个区间内总共只有这 5 个强大整数。

示例 2:

输入:start = 15, finish = 215, limit = 6, s = "10"
输出:2
解释:区间 [15..215] 内的强大整数为 110 和 210 。这些整数的各个数位都 <= 6 且 "10" 是它们的后缀。
这个区间总共只有这 2 个强大整数。

示例 3:

输入:start = 1000, finish = 2000, limit = 4, s = "3000"
输出:0
解释:区间 [1000..2000] 内的整数都小于 3000 ,所以 "3000" 不可能是这个区间内任何整数的后缀。

说明:

  • 1 <= start <= finish <= 10^15
  • 1 <= limit <= 9
  • 1 <= s.length <= floor(log10(finish)) + 1
  • s 数位中每个数字都小于等于 limit 。
  • s 不包含任何前导 0 。

思路

返回指定区间 [start, finish] 内,后缀为 s 且每个数字不超过 limit 的数字个数。

数位dp,需要特殊处理后缀,比如 s = 10,start = 101, finish = 521 还剩两位时,01 < 10, 21 > 10 都不能计数。

代码


/**
 * @date 2025-04-10 20:19
 */
public class NumberOfPowerfulInt2999 {

    public long numberOfPowerfulInt(long start, long finish, int limit, String s) {
        long suffix = Long.parseLong(s);
        if (finish < suffix) {
            return 0L;
        }
        int[] high = Long.toString(finish).chars().map(x -> x - '0').toArray();
        int hl = high.length;
        long[] mem = new long[hl];
        int[] low = new int[hl--];
        long tmp = start;
        while (tmp > 0) {
            low[hl--] = (int) (tmp % 10);
            tmp /= 10;
        }
        Arrays.fill(mem, -1L);
        return dfs(0, low, high, true, true, mem, limit, s);
    }

    public long dfs(int index, int[] low, int[] high, boolean isLowLimit, boolean isHighLimit, long[] mem, int limit, String s) {
        if (index == high.length - s.length()) {
            boolean unaviable = false;
            if (isHighLimit) {
                StringBuilder hr = new StringBuilder();
                int tmp = index;
                while (tmp < high.length) {
                    hr.append(high[tmp++]);
                }
                unaviable = Long.parseLong(hr.toString()) < Long.parseLong(s);
            }
            if (isLowLimit) {
                StringBuilder lr = new StringBuilder();
                while (index < high.length) {
                    lr.append(low[index++]);
                }
                unaviable = unaviable || Long.parseLong(lr.toString()) > Long.parseLong(s);
            }
            return unaviable ? 0 : 1;
        }
        if (!isLowLimit && !isHighLimit && mem[index] != -1) {
            return mem[index];
        }
        long res = 0;
        int up = isHighLimit ? Math.min(high[index], limit) : limit;
        int down = isLowLimit ? low[index] : 0;

        for (int i = down; i <= up; i++) {
            res += dfs(index + 1, low, high, isLowLimit && i == low[index], isHighLimit && i == high[index], mem, limit, s);
        }

        if (!isHighLimit && !isLowLimit) {
            mem[index] = res;
        }
        return res;
    }

}

性能