leetcode
leetcode 801 ~ 850
第 N 个神奇数字

第 N 个神奇数字

难度:

标签:

题目描述

代码结果

运行时间: 25 ms, 内存: 16.0 MB


/*
 * 思路:
 * 虽然Java Streams不适合处理这样的问题,但我们可以借助一些额外的工具类来使用流的方式进行计算。
 * 我们可以模拟流操作,但二分查找仍然是核心。
 */

import java.util.stream.LongStream;

public class MagicalNumberStream {
    private static final int MOD = 1000000007;

    public int nthMagicalNumber(int n, int a, int b) {
        long low = Math.min(a, b);
        long high = (long) n * Math.min(a, b);
        long lcm = lcm(a, b);

        while (low < high) {
            long mid = low + (high - low) / 2;
            if (LongStream.of(mid / a, mid / b).sum() - mid / lcm < n) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }

        return (int) (low % MOD);
    }

    private long gcd(long x, long y) {
        if (y == 0) return x;
        return gcd(y, x % y);
    }

    private long lcm(long x, long y) {
        return x * (y / gcd(x, y));
    }

    public static void main(String[] args) {
        MagicalNumberStream mn = new MagicalNumberStream();
        System.out.println(mn.nthMagicalNumber(1, 2, 3)); // 输出 2
        System.out.println(mn.nthMagicalNumber(4, 2, 3)); // 输出 6
    }
}

解释

方法:

这道题要找出第n个可以被a或b整除的神奇数字。题解使用二分搜索方法,设定搜索区间的左界为min(a, b)(因为这是第一个可能的神奇数字),右界为n * min(a, b)(这是一个保守的上界,理论上第n个神奇数字不会超过这个值)。中点mid在搜索过程中被检查,看通过a或b除以它可以得到多少个神奇数字。这个计数由mid // a + mid // b - mid // lcm(a, b)给出,后者是为了排除重复计数的情况(即同时被a和b整除的情况)。如果这个计数大于或等于n,说明第n个神奇数字小于或等于mid,因此减小右界。否则,增加左界。二分搜索结束后,r+1即是所求的第n个神奇数字。

时间复杂度:

O(log(n))

空间复杂度:

O(1)

代码细节讲解

🦆
为什么在计算第n个神奇数字时选择二分搜索法而不是直接从min(a, b)开始逐个检查?
直接从min(a, b)开始逐个检查虽然直观,但效率很低,特别是当n很大或a和b的值很大时。二分搜索法可以显著提高效率,因为它每次将搜索区间减半,从而在对数时间复杂度内找到答案。这种方法利用了数学上的性质,可以快速计算在任意数字x之下,有多少个数可以被a或b整除,从而判断第n个神奇数字是更大还是更小,有效地缩小搜索范围。
🦆
在计算mid时,为什么要减去`mid // lcm(a, b)`来排除重复计数?请解释其数学原理。
在计算有多少个神奇数字时,单独计算`mid // a`和`mid // b`会重复计数那些同时被a和b整除的数字。最小公倍数`lcm(a, b)`是同时被a和b整除的最小数,因此`mid // lcm(a, b)`给出了在mid之下,同时被a和b整除的数字数量。为了得到准确的神奇数字数量,需要从总数中减去这部分重复计数的数字。这是根据包容排斥原理,确保每个数字只被计数一次。
🦆
设置搜索区间的右界为`n * min(a, b)`是基于什么考虑?是否有可能这个值小于第n个神奇数字?
设置右界为`n * min(a, b)`是一个保守的估计,它基于假设所有的神奇数字都是最小可能值`min(a, b)`的倍数。这确保了在最坏情况下(即所有神奇数字均为这一最小值的倍数时),搜索区间仍然包含第n个神奇数字。实际上,由于数字可以同时被a和b整除,所以实际的第n个神奇数字通常会小于这个上界。因此,这个值不大可能小于第n个神奇数字,而是提供了一个足够大的范围以确保包含解。
🦆
二分搜索结束时为什么返回的是`(r + 1) % const`而不直接是`r % const`?
在二分搜索过程中,如果中间的计数`cnt`大于或等于n,我们会减小右界`r`到`mid - 1`。这意味着最后一次`cnt >= n`时的`mid`实际上可能正是我们需要的解,但此时`r`已经被设置为`mid - 1`。因此,搜索结束时,实际的答案应该是`r + 1`。使用`(r + 1) % const`是为了防止结果超过题目规定的模数`10**9 + 7`,保持结果的正确性和范围。

相关问题