3483.不同三位偶数的数目

目标

给你一个数字数组 digits,你需要从中选择三个数字组成一个三位偶数,你的任务是求出 不同 三位偶数的数量。

注意:每个数字在三位偶数中都只能使用 一次 ,并且 不能 有前导零。

示例 1:

输入: digits = [1,2,3,4]
输出: 12
解释: 可以形成的 12 个不同的三位偶数是 124,132,134,142,214,234,312,314,324,342,412 和 432。注意,不能形成 222,因为数字 2 只有一个。

示例 2:

输入: digits = [0,2,2]
输出: 2
解释: 可以形成的三位偶数是 202 和 220。注意,数字 2 可以使用两次,因为数组中有两个 2 。

示例 3:

输入: digits = [6,6,6]
输出: 1
解释: 只能形成 666。

示例 4:

输入: digits = [1,3,5]
输出: 0
解释: 无法形成三位偶数。

说明:

  • 3 <= digits.length <= 10
  • 0 <= digits[i] <= 9

思路

有一个数字数组 digits,数组元素为 0 ~ 9 ,从中选择 3 个组成一个三位偶数,求不同的三位偶数的个数。

从结果来考虑,三位数 100 ~ 999,针对每个数字判断能否由数组中的数字构成即可。

从构造的角度考虑,需要保证个位是偶数,且百位不是 0,要求数字不同,需要维护可用的数字种类,需要知道每个数字的已使用次数,可以使用回溯。

代码


/**
 * @date 2026-09-11 8:56
 */
public class TotalNumbers3483 {

    public int totalNumbers(int[] digits) {
        int[] d = new int[10];
        for (int num : digits) {
            d[num]++;
        }
        int res = 0;
        for (int num = 100; num < 999; num += 2) {
            if (valid(num, d)) {
                res++;
            }
        }
        return res;
    }

    public boolean valid(int num, int[] d) {
        int[] tmp = new int[10];
        while (num > 0) {
            tmp[num % 10]++;
            num /= 10;
        }
        for (int i = 0; i < 10; i++) {
            if (d[i] < tmp[i]) {
                return false;
            }
        }
        return true;
    }

    public int totalNumbers_v0(int[] digits) {
        int[] d = new int[10];
        for (int num : digits) {
            d[num]++;
        }
        int res = 0;
        // 枚举百位
        for (int i = 1; i < d.length; i++) {
            if (d[i] == 0) {
                continue;
            }
            d[i]--;
            res += dfs(0, i, d);
            d[i]++;
        }

        return res;
    }

    int dfs(int index, int num, int[] d) {
        if (index == 2) {
            // 第三个数如果是偶数返回 1
            return num % 2 == 0 ? 1 : 0;
        }
        int res = 0;
        // 枚举十位
        for (int i = 0; i < d.length; i++) {
            if (d[i] == 0) {
                continue;
            }
            d[i]--;
            res += dfs(index + 1, i, d);
            d[i]++;
        }
        return res;
    }

}

性能

3871.统计范围内的逗号II

目标

给你一个整数 n。

返回将所有从 [1, n](包含两端)范围内的整数以 标准 数字格式书写时所用到的 逗号总数。

在 标准 格式中:

  • 从右边开始,每 三位 数字后插入一个逗号。
  • 位数 少于四位 的数字不包含逗号。

示例 1:

输入: n = 1002
输出: 3
解释:
数字 "1,000"、"1,001" 和 "1,002" 每个都包含一个逗号,总计 3 个逗号。

示例 2:

输入: n = 998
输出: 0
解释:
从 1 到 998 的所有数字位数都少于四位,因此没有使用逗号。

说明:

  • 1 <= n <= 10^15

思路

返回 [1, n] 之间所有整数的标准写法中总共有多少逗号。所谓标准写法指从右开始每 3 个数字插入一个逗号,且逗号不能位于开头。

  • 1,000 ~ 999,999 之间的数字有 1 个逗号
  • 1,000,000 ~ 999,999,999 之间的数字有 2 个逗号
  • 1,000,000,000 ~ 999,999,999,999 之间的数字有 3 个逗号
  • 1,000,000,000,000 ~ 999,999,999,999,999 之间的数字有 4 个逗号
  • 1,000,000,000,000,0005 个逗号

返回 max(0, n - 999) + max(0, n - 999999) + max(0, n - 999999999) + max(0, n - 999999999999) + max(0, n - 999999999999999)

可以写成循环的形式,for (long i = 1000; i <= n; i *= 1000) { res += n - i + 1; }

代码


/**
 * @date 2026-09-08 9:15
 */
public class CountCommas3871 {

    public long countCommas(long n) {
        long res = 0;
        for (long i = 1000; i <= n; i *= 1000) {
            res += n - i + 1;
        }
        return res;
    }
}

性能

3870.统计范围内的逗号

目标

给你一个整数 n。

返回将所有从 [1, n](包含两端)范围内的整数以 标准 数字格式书写时所用到的 逗号总数。

在 标准 格式中:

  • 从右边开始,每 三位 数字后插入一个逗号。
  • 位数 少于四位 的数字不包含逗号。

示例 1:

输入: n = 1002
输出: 3
解释:
数字 "1,000"、"1,001" 和 "1,002" 每个都包含一个逗号,总计 3 个逗号。

示例 2:

输入: n = 998
输出: 0
解释:
从 1 到 998 的所有数字位数都少于四位,因此没有使用逗号。

说明:

  • 1 <= n <= 10^5

思路

返回 [1, n] 之间所有整数的标准写法中总共有多少逗号。所谓标准写法指从右开始每 3 个数字插入一个逗号,且逗号不能位于开头。

只需判断 1 ~ n 之间有多少个数字在 1,000 ~ 100,000 之间,返回 max(0, n - 999) 即可。

代码


/**
 * @date 2026-09-08 9:04
 */
public class CountCommas3870 {

    public int countCommas(int n) {
        return Math.max(0, n - 999);
    }
}

性能

3731.找出缺失的元素

目标

给你一个整数数组 nums ,数组由若干 互不相同 的整数组成。

数组 nums 原本包含了某个范围内的 所有整数 。但现在,其中可能 缺失 部分整数。

该范围内的 最小 整数和 最大 整数仍然存在于 nums 中。

返回一个 有序 列表,包含该范围内缺失的所有整数,并 按从小到大排序。如果没有缺失的整数,返回一个 空 列表。

示例 1:

输入: nums = [1,4,2,5]
输出: [3]
解释:
最小整数为 1,最大整数为 5,因此完整的范围应为 [1,2,3,4,5]。其中只有 3 缺失。

示例 2:

输入: nums = [7,8,6,9]
输出: []
解释:
最小整数为 6,最大整数为 9,因此完整的范围为 [6,7,8,9]。所有整数均已存在,因此没有缺失的整数。

示例 3:

输入: nums = [5,1]
输出: [2,3,4]
解释:
最小整数为 1,最大整数为 5,因此完整的范围应为 [1,2,3,4,5]。缺失的整数为 2、3 和 4。

说明:

  • 2 <= nums.length <= 100
  • 1 <= nums[i] <= 100

思路

有一个元素互不相同的数组,原本包含了 [min, max] 之间的所有整数,现在缺失了 (min, max) 中的一些元素,找到并返回缺失的元素。

首先找到数组的 minmax,将元素放入哈希表,遍历 (min, max) 判断数字是否在集合中。

代码


/**
 * @date 2026-08-04 8:49
 */
public class FindMissingElements3731 {

    public List<Integer> findMissingElements(int[] nums) {
        int min = 101, max = 0;
        Set<Integer> set = new HashSet<>();
        for (int num : nums) {
            min = Math.min(min, num);
            max = Math.max(max, num);
            set.add(num);
        }
        List<Integer> res = new ArrayList<>();
        for (int i = min + 1; i < max; i++) {
            if (!set.contains(i)) {
                res.add(i);
            }
        }
        return res;
    }
}

性能

3514.不同 XOR 三元组的数目II

目标

给你一个整数数组 nums 。

XOR 三元组 定义为三个元素的异或值 nums[i] XOR nums[j] XOR nums[k],其中 i <= j <= k。

返回所有可能三元组 (i, j, k) 中 不同 的 XOR 值的数量。

示例 1:

输入: nums = [1,3]
输出: 2
解释:
所有可能的 XOR 三元组值为:
(0, 0, 0) → 1 XOR 1 XOR 1 = 1
(0, 0, 1) → 1 XOR 1 XOR 3 = 3
(0, 1, 1) → 1 XOR 3 XOR 3 = 1
(1, 1, 1) → 3 XOR 3 XOR 3 = 3
不同的 XOR 值为 {1, 3} 。因此输出为 2 。

示例 2:

输入: nums = [6,7,8,9]
输出: 4
解释:
不同的 XOR 值为 {6, 7, 8, 9} 。因此输出为 4 。

说明:

1 <= nums.length <= 1500
1 <= nums[i] <= 1500

思路

有一个正整数数组 nums,从中取三个数(可重复)的异或值 XOR,求不同的 XOR 值有多少个。

暴力枚举。

代码


/**
 * @date 2026-07-24 9:12
 */
public class UniqueXorTriplets3514 {

    public int uniqueXorTriplets(int[] nums) {
        int n = nums.length;
        int max = 0;
        for (int num : nums) {
            max = Math.max(max, num);
        }
        int upper = 1 << (32 - Integer.numberOfLeadingZeros(max));
        boolean[] tmp = new boolean[upper];
        boolean[] arr = new boolean[upper];
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                tmp[nums[i] ^ nums[j]] = true;
            }
        }
        for (int i = 0; i < upper; i++) {
            if (tmp[i]) {
                for (int num : nums) {
                    arr[num ^ i] = true;
                }
            }
        }
        int res = 0;
        for (boolean b : arr) {
            if (b) {
                res++;
            }
        }
        return res;
    }

}

性能

3499.操作后最大活跃区段数I

目标

给你一个长度为 n 的二进制字符串 s,其中:

  • '1' 表示一个 活跃 区段。
  • '0' 表示一个 非活跃 区段。

你可以执行 最多一次操作 来最大化 s 中的活跃区段数量。在一次操作中,你可以:

  • 将一个被 '0' 包围的连续 '1' 区块转换为全 '0'。
  • 然后,将一个被 '1' 包围的连续 '0' 区块转换为全 '1'。

返回在执行最优操作后,s 中的 最大 活跃区段数。

注意:处理时需要在 s 的两侧加上 '1' ,即 t = '1' + s + '1'。这些加上的 '1' 不会影响最终的计数。

示例 1:

输入: s = "01"
输出: 1
解释:
因为没有被 '0' 包围的 '1' 区块,因此无法进行有效操作。最大活跃区段数为 1。

示例 2:

输入: s = "0100"
输出: 4
解释:
字符串 "0100" → 两端加上 '1' 后得到 "101001" 。
选择 "0100","101001" → "100001" → "111111" 。
最终的字符串去掉两端的 '1' 后为 "1111" 。最大活跃区段数为 4。

示例 3:

输入: s = "1000100"
输出: 7
解释:
字符串 "1000100" → 两端加上 '1' 后得到 "110001001" 。
选择 "000100","110001001" → "110000001" → "111111111"。
最终的字符串去掉两端的 '1' 后为 "1111111"。最大活跃区段数为 7。

示例 4:

输入: s = "01010"
输出: 4
解释:
字符串 "01010" → 两端加上 '1' 后得到 "1010101"。
选择 "010","1010101" → "1000101" → "1111101"。
最终的字符串去掉两端的 '1' 后为 "11110"。最大活跃区段数为 4。

说明:

  • 1 <= n == s.length <= 10^5
  • s[i] 仅包含 '0' 或 '1'

思路

有一个二进制字符串,在其首尾拼上 1,然后执行一次操作:将一个被 0 包围的连续 1 全部转为 0,然后将一个被 1 包围的连续 0 全部转为 1,求操作之后 字符串1 的最大个数(不包括首尾拼接的 1)。

实际上是求字符串中 1 两侧连续 0 的最大长度。

判断 01 分界,记录之前连续 0 的个数与当前连续 0 的个数之和的最大值。

代码


/**
 * @date 2026-07-21 10:41
 */
public class MaxActiveSectionsAfterTrade3499 {

    public int maxActiveSectionsAfterTrade(String s) {
        int n = s.length();
        int prevZero = 0;
        int curZero = 0;
        int oneCnt = 0;
        int max = 0;
        for (int i = 0; i < n; i++) {
            if (s.charAt(i) == '1') {
                oneCnt++;
                if (curZero > 0) {
                    if (prevZero > 0) {
                        max = Math.max(max, curZero + prevZero);
                    }
                    prevZero = curZero;
                    curZero = 0;
                }
            } else {
                curZero++;
            }
        }
        if (curZero > 0) {
            if (prevZero > 0) {
                max = Math.max(max, curZero + prevZero);
            }
        }
        return oneCnt + max;
    }

}

性能

1291.顺次数

目标

我们定义「顺次数」为:每一位上的数字都比前一位上的数字大 1 的整数。

请你返回由 [low, high] 范围内所有顺次数组成的 有序 列表(从小到大排序)。

示例 1:

输出:low = 100, high = 300
输出:[123,234]

示例 2:

输出:low = 1000, high = 13000
输出:[1234,2345,3456,4567,5678,6789,12345]

说明:

  • 10 <= low <= high <= 10^9

思路

定义顺序数字为从左到右每一位都比前一位的数字大 1 的整数。返回 [low, high] 范围内的所有顺序数字,按从小到大返回。

枚举数字长度以及开头数字,判断生成的顺序数字是否在 [low, high] 内即可。

代码


/**
 * @date 2026-07-13 9:10
 */
public class SequentialDigits1291 {

    public List<Integer> sequentialDigits(int low, int high) {
        int l = Integer.toString(low).length();
        int r = Integer.toString(high).length();
        List<Integer> res = new ArrayList<>();
        for (int i = l; i <= r; i++) {
            for(int j = 1; j + i <= 10; j++){
                Integer num = genInteger(j, i);
                if (low <= num && num <= high){
                    res.add(num);
                }
            }
        }
        return res;
    }

    public Integer genInteger(int first, int length) {
        int res = 0;
        for (int i = 0; i < length && first < 10; i++) {
            res = res * 10 + first;
            first++;
        }
        return res;
    }

}

性能

3020.子集中元素的最大数量

目标

给你一个 正整数 数组 nums 。

你需要从数组中选出一个满足下述条件的子集:

  • 你可以将选中的元素放置在一个下标从 0 开始的数组中,并使其遵循以下模式:[x, x^2, x^4, ..., x^k/2, x^k, x^k/2, ..., x^4, x^2, x](注意,k 可以是任何 非负 的 2 的幂)。例如,[2, 4, 16, 4, 2] 和 [3, 9, 3] 都符合这一模式,而 [2, 4, 8, 4, 2] 则不符合。

返回满足这些条件的子集中,元素数量的 最大值 。

示例 1:

输入:nums = [5,4,1,2,2]
输出:3
解释:选择子集 {4,2,2} ,将其放在数组 [2,4,2] 中,它遵循该模式,且 22 == 4 。因此答案是 3 。

示例 2:

输入:nums = [1,3,2,4]
输出:1
解释:选择子集 {1},将其放在数组 [1] 中,它遵循该模式。因此答案是 1 。注意我们也可以选择子集 {2} 、{4} 或 {3} ,可能存在多个子集都能得到相同的答案。

说明:

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

思路

将正整数数组 nums 的子序列按照模式放入一个下标从 0 开始的数组中,模式为 [x x^2 x^4 x^8 ... x^k/2 x^k x^k/2 ... x^8 x^4 x^2 x]k 可以是任何非负的 2 的幂,即 1、2、4、8、16……。求满足条件的子序列的最大长度。

对数组中的元素计数,如果 x 出现次数大于等于 2 则开始倍增,将长度加 2,判断 x^2 是否出现,如果出现次数大于 1,则判断 (x^2)^2 是否出现,以此类推。循环结束时判断数字的出现次数,如果为 0 需要将前面的元素作为中间元素,之前长度加了 2 这里需要减 1,如果为 1 正好作为中间元素将长度加 1

代码


/**
 * @date 2026-06-29 17:33
 */
public class MaximumLength3020 {

    public int maximumLength(int[] nums) {
        Map<Integer, Integer> cnt = new HashMap<>();
        for (int num : nums) {
            cnt.merge(num, 1, Integer::sum);
        }
        int res = 1;
        if (cnt.get(1) != null) {
            res = Math.max(res, cnt.get(1) - (1 - cnt.get(1) % 2));
            cnt.remove(1);
        }
        for (Integer num : cnt.keySet()) {
            int l = 0;
            while (cnt.getOrDefault(num, 0) > 1) {
                l += 2;
                num *= num;
            }
            res = Math.max(res, l + (cnt.get(num) == null ? -1 : 1));
        }
        return res;
    }

}

性能

3633.最早完成陆地和水上游乐设施的时间I

目标

给你两种类别的游乐园项目:陆地游乐设施 和 水上游乐设施。

  • 陆地游乐设施
    • landStartTime[i] – 第 i 个陆地游乐设施最早可以开始的时间。
    • landDuration[i] – 第 i 个陆地游乐设施持续的时间。
  • 水上游乐设施
    • waterStartTime[j] – 第 j 个水上游乐设施最早可以开始的时间。
    • waterDuration[j] – 第 j 个水上游乐设施持续的时间。

一位游客必须从 每个 类别中体验 恰好一个 游乐设施,顺序 不限 。

  • 游乐设施可以在其开放时间开始,或 之后任意时间 开始。
  • 如果一个游乐设施在时间 t 开始,它将在时间 t + duration 结束。
  • 完成一个游乐设施后,游客可以立即乘坐另一个(如果它已经开放),或者等待它开放。

返回游客完成这两个游乐设施的 最早可能时间 。

示例 1:

输入:landStartTime = [2,8], landDuration = [4,1], waterStartTime = [6], waterDuration = [3]
输出:9
解释:
方案 A(陆地游乐设施 0 → 水上游乐设施 0):
在时间 landStartTime[0] = 2 开始陆地游乐设施 0。在 2 + landDuration[0] = 6 结束。
水上游乐设施 0 在时间 waterStartTime[0] = 6 开放。立即在时间 6 开始,在 6 + waterDuration[0] = 9 结束。
方案 B(水上游乐设施 0 → 陆地游乐设施 1):
在时间 waterStartTime[0] = 6 开始水上游乐设施 0。在 6 + waterDuration[0] = 9 结束。
陆地游乐设施 1 在 landStartTime[1] = 8 开放。在时间 9 开始,在 9 + landDuration[1] = 10 结束。
方案 C(陆地游乐设施 1 → 水上游乐设施 0):
在时间 landStartTime[1] = 8 开始陆地游乐设施 1。在 8 + landDuration[1] = 9 结束。
水上游乐设施 0 在 waterStartTime[0] = 6 开放。在时间 9 开始,在 9 + waterDuration[0] = 12 结束。
方案 D(水上游乐设施 0 → 陆地游乐设施 0):
在时间 waterStartTime[0] = 6 开始水上游乐设施 0。在 6 + waterDuration[0] = 9 结束。
陆地游乐设施 0 在 landStartTime[0] = 2 开放。在时间 9 开始,在 9 + landDuration[0] = 13 结束。
方案 A 提供了最早的结束时间 9。

示例 2:

输入:landStartTime = [5], landDuration = [3], waterStartTime = [1], waterDuration = [10]
输出:14
解释:
方案 A(水上游乐设施 0 → 陆地游乐设施 0):
在时间 waterStartTime[0] = 1 开始水上游乐设施 0。在 1 + waterDuration[0] = 11 结束。
陆地游乐设施 0 在 landStartTime[0] = 5 开放。立即在时间 11 开始,在 11 + landDuration[0] = 14 结束。
方案 B(陆地游乐设施 0 → 水上游乐设施 0):
在时间 landStartTime[0] = 5 开始陆地游乐设施 0。在 5 + landDuration[0] = 8 结束。
水上游乐设施 0 在 waterStartTime[0] = 1 开放。立即在时间 8 开始,在 8 + waterDuration[0] = 18 结束。
方案 A 提供了最早的结束时间 14。​​​​​​​

说明:

  • 1 <= n, m <= 100
  • landStartTime.length == landDuration.length == n
  • waterStartTime.length == waterDuration.length == m
  • 1 <= landStartTime[i], landDuration[i], waterStartTime[j], waterDuration[j] <= 1000

思路

有两种游乐场项目,landStartTime[i]landDuration[i] 分别表示陆上项目 i 的开始时间与持续时间,waterStartTime[i]waterDuration[i] 分别表示水上项目 i 的开始时间与持续时间。游客需要分别游玩一个陆上项目和一个水上项目,返回最早的结束时间。

暴力枚举每一个陆上项目,与每一个水上项目的开始结束区间比较,如果不相交,结束时间是最大的结束时间,否则最早结束时间为最早的开始时间加上它们的持续时间。

代码


/**
 * @date 2026-06-02 23:45
 */
public class EarliestFinishTime3633 {

    public int earliestFinishTime(int[] landStartTime, int[] landDuration, int[] waterStartTime, int[] waterDuration) {
        int n = landStartTime.length;
        int m = waterStartTime.length;
        int res = Integer.MAX_VALUE;
        for (int i = 0; i < n; i++) {
            int start = landStartTime[i];
            int end = landStartTime[i] + landDuration[i];
            for (int j = 0; j < m; j++) {
                int ws = waterStartTime[j];
                int we = ws + waterDuration[j];
                if (start >= we) {
                    res = Math.min(res, end);
                } else if (end <= ws) {
                    res = Math.min(res, we);
                } else {
                    res = Math.min(res, Math.min(ws, start) + landDuration[i] + waterDuration[j]);
                }
            }
        }
        return res;
    }
}

性能

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

性能