1399.统计最大组的数目

目标

给你一个整数 n 。请你先求出从 1 到 n 的每个整数 10 进制表示下的数位和(每一位上的数字相加),然后把数位和相等的数字放到同一个组中。

请你统计每个组中的数字数目,并返回数字数目并列最多的组有多少个。

示例 1:

输入:n = 13
输出:4
解释:总共有 9 个组,将 1 到 13 按数位求和后这些组分别是:
[1,10],[2,11],[3,12],[4,13],[5],[6],[7],[8],[9]。总共有 4 个组拥有的数字并列最多。

示例 2:

输入:n = 2
输出:2
解释:总共有 2 个大小为 1 的组 [1],[2]。

示例 3:

输入:n = 15
输出:6

示例 4:

输入:n = 24
输出:5

说明:

  • 1 <= n <= 10^4

思路

计算 1 ~ n 十进制下的数位和,将数位和相同的分成到一组,求组中数字个数并列最多的组有多少个。

根据题意模拟即可,也可以使用数位DP。

代码


/**
 * @date 2025-04-23 0:12
 */
public class CountLargestGroup1399 {

    public int countLargestGroup(int n) {
        int length = String.valueOf(n).length();
        int[] cnt = new int[9 * length + 1];
        int maxDigitSumCnt = 0;
        int res = 0;
        for (int i = 1; i <= n; i++) {
            int num = i;
            int sum = 0;
            while (num > 0) {
                sum += num % 10;
                num /= 10;
            }
            if (maxDigitSumCnt < ++cnt[sum]) {
                maxDigitSumCnt = cnt[sum];
                res = 1;
            } else if (maxDigitSumCnt == cnt[sum]) {
                res++;
            }
        }
        return res;
    }

}

性能

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

}

性能

1742.盒子中小球的最大数量

目标

你在一家生产小球的玩具厂工作,有 n 个小球,编号从 lowLimit 开始,到 highLimit 结束(包括 lowLimit 和 highLimit ,即 n == highLimit - lowLimit + 1)。另有无限数量的盒子,编号从 1 到 infinity 。

你的工作是将每个小球放入盒子中,其中盒子的编号应当等于小球编号上每位数字的和。例如,编号 321 的小球应当放入编号 3 + 2 + 1 = 6 的盒子,而编号 10 的小球应当放入编号 1 + 0 = 1 的盒子。

给你两个整数 lowLimit 和 highLimit ,返回放有最多小球的盒子中的小球数量。如果有多个盒子都满足放有最多小球,只需返回其中任一盒子的小球数量。

示例 1:

输入:lowLimit = 1, highLimit = 10
输出:2
解释:
盒子编号:1 2 3 4 5 6 7 8 9 10 11 ...
小球数量:2 1 1 1 1 1 1 1 1 0  0  ...
编号 1 的盒子放有最多小球,小球数量为 2 。

示例 2:

输入:lowLimit = 5, highLimit = 15
输出:2
解释:
盒子编号:1 2 3 4 5 6 7 8 9 10 11 ...
小球数量:1 1 1 1 2 2 1 1 1 0  0  ...
编号 5 和 6 的盒子放有最多小球,每个盒子中的小球数量都是 2 。

示例 3:

输入:lowLimit = 19, highLimit = 28
输出:2
解释:
盒子编号:1 2 3 4 5 6 7 8 9 10 11 12 ...
小球数量:0 1 1 1 1 1 1 1 1 2  0  0  ...
编号 10 的盒子放有最多小球,小球数量为 2 。

说明:

  • 1 <= lowLimit <= highLimit <= 10^5

思路

n 个编号从 lowLimithighLimit 的小球,同时有编号 [1, +∞] 的盒子。将小球放入它编号数位之和对应的盒子中,求放有最多小球的盒子中的小球数量。

暴力做法是针对每一个数字计算其数位之和。时间复杂度为 O(l * n)l 表示数字的长度,最大 6 位数字,总规模为 6 * 10^5 可行。

注意到编号是连续的,如果编号没有进位,那么盒号加 1;如果编号进位,盒号进位加 1,还要减去原来编号末尾的 9,有几个 9 就减几个。省去了每一个数的数位求和。

代码


/**
 * @date 2025-02-13 0:57
 */
public class CountBalls1742 {

    public int countBalls_v2(int lowLimit, int highLimit) {
        int boxNo = 0;
        int num = lowLimit;
        while (num > 0) {
            boxNo += num % 10;
            num /= 10;
        }
        int[] cnt = new int[46];
        for (int i = lowLimit; i <= highLimit; i++) {
            cnt[boxNo++]++;
            int tmp = i;
            while (tmp % 10 == 9) {
                boxNo -= 9;
                tmp /= 10;
            }
        }
        return Arrays.stream(cnt).max().getAsInt();
    }

    public int countBalls(int lowLimit, int highLimit) {
        Map<Integer,Integer> map = new HashMap<>();
        for(int i = lowLimit; i <= highLimit; i++){
            int sum = 0;
            int num = i;
            while(num > 0){
                sum += num % 10;
                num /= 10;
            }
            map.merge(sum, 1, Integer::sum);
        }
        return map.values().stream().max(Integer::compareTo).orElse(0);
    }
}

性能