力扣 2719. 统计整数数目

题目地址:https://leetcode.cn/problems/count-of-integers/
递归核心是枚举统计,结合记忆化搜索节省时间。

以数字 3216 为例,从 [0, 0, 0, 0] 开始枚举,到 [2, 1, 6, X] 时,i = 2,sum = 2 + 1 + 6 = 9,对 X 进行 dfs 时分别枚举 sum + X 代入,根据 min_sum 和 max_sum 统计返回,接着将结果存储到 memo[2][9] 给 [2, 2, 5, X] 等复用,继续递归。

image.png

image.png

image.png

/**
 * 时间 O(10mn)  96ms  100%
 * 空间 O(mn)    46mb  88%
 */
function count(num1: string, num2: string, min_sum: number, max_sum: number): number {
    const MOD = 1e9 + 7;
    const ASCII0 = '0'.charCodeAt(0);
    const ans = _count(num2) - _count(num1) + isValid(num1) + MOD;    // 避免余数加减得到的负数
    // (a + b) % c = (a % c + b % c) % c;
    return ans % MOD;

    /** 判断单个数是否合法 */
    function isValid(num: string): number {
        let res = 0;
        for (let i = 0; i < num.length; i++) {
            res += num.charCodeAt(i) - ASCII0;
        }
        return res >= min_sum && res <= max_sum ? 1 : 0;
    }

    /** 获取 num 以内的合法数数量 */
    function _count(num: string): number {
        const n = num.length;
        const memo = new Array(n);
        for (let i = 0; i < n; i++) {
            // 每一位的记忆化存储上限是 9n 或 max_sum, 因为超过 max_sum 就过滤掉了
            // 直接记忆在 sum 位, 所以 +1
            memo[i] = new Array(Math.min(9 * n, max_sum) + 1).fill(-1);
        }
        // 第一位一定是 limit 的
        return f(num, memo, 0, 0, true);
    }

    // 从数字第 i 位开始计算满足条件的 count
    // 这里递归栈为 n 层
    function f(s: string, memo: number[][], i: number, sum: number, isLimit: boolean): number {
        // 递归求和超出上限, 停止后续计算
        if (sum > max_sum) return 0;

        // 递归求和进行到最后一位, 满足条件返回 1 值
        if (i === s.length) return sum >= min_sum ? 1 : 0;

        // 记忆化存储
        if (!isLimit && memo[i][sum] !== -1) return memo[i][sum];
        
        // 累加和
        let res = 0;

        // 当前位上限
        const up = isLimit ? s.charCodeAt(i) - ASCII0 : 9;

        // 枚举当前位可填数字
        for (let j = 0; j <= up; j++) {
            // 转移方程:累加 i + 1 位满足条件的 count
            // (a + b) % c = (a % c + b % c) % c;
            res = (res + f(s, memo, i + 1, sum + j, isLimit && j === up)) % MOD;
        }

        // 记忆化存储累加和
        if (!isLimit) memo[i][sum] = res;

        return res;
    }
};
### LeetCode 第 315 题:计算右侧小于当前元素的个数 LeetCode 第 315 题的目标是,给定一个整数数组 `nums`,返回一个新的计数数组 `count`,其中 `count[i]` 表示在 `nums[i]` 右侧比它小的元素的数目。这个问题可以通过多种方法解决,例如归并排序、二叉索引树(Fenwick Tree)或线段树等。 #### 使用归并排序的方法实现 这种方法的核心思想是在归并排序的过程中记录每个元素右侧比它小的元素数量。归并排序中的合并步骤可以帮助我们高效地统计这些信息。 以下是使用归并排序的 Java 解法: ```java import java.util.*; public class Solution { private int[] count; public List<Integer> countSmaller(int[] nums) { int n = nums.length; count = new int[n]; int[] indices = new int[n]; for (int i = 0; i < n; i++) { indices[i] = i; } mergeSort(nums, indices, 0, n - 1); List<Integer> result = new ArrayList<>(); for (int c : count) { result.add(c); } return result; } private void mergeSort(int[] nums, int[] indices, int left, int right) { if (right - left <= 1) { return; } int mid = (left + right) / 2; mergeSort(nums, indices, left, mid); mergeSort(nums, indices, mid, right); merge(nums, indices, left, mid, right); } private void merge(int[] nums, int[] indices, int left, int mid, int right) { int[] temp = new int[right - left + 1]; int i = left, j = mid, k = 0; int[] smallerCount = new int[right - left + 1]; while (i < mid && j <= right) { if (nums[indices[i]] <= nums[indices[j]]) { // 记录右侧比当前元素小的数目 smallerCount[k] = j - mid; temp[k++] = indices[i++]; } else { temp[k++] = indices[j++]; } } while (i < mid) { smallerCount[k] = j - mid; temp[k++] = indices[i++]; } while (j <= right) { temp[k++] = indices[j++]; } // 更新 count 数组 for (int p = 0; p < temp.length; p++) { nums[temp[p]] += smallerCount[p]; // 累加右侧较小元素的数量 count[temp[p]] += nums[temp[p]]; } } } ``` #### 思路说明: - **归并排序**:通过递归将数组分割成最小单元后,在合并过程中统计右侧比当前元素小的数目。 - **索引数组**:为了避免直接修改原始数组,我们使用索引数组来跟踪元素的位置。 - **辅助数组 `smallerCount`**:在合并过程中,利用该数组记录每个元素右侧较小的数目,并最终更新到 `count` 数组中。 #### 时间复杂度分析 - 归并排序的时间复杂度为 $O(n \log n)$,因此此解法可以高效处理大规模输入数据 [^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值