🎮 每日一练-区间合并问题(附快速幂)

🤔 区间合并的思路:首先按照左端点从小到大对区间进行排序,然后对于第 i 个区间,不断地向右扩展与它有交集的区间。

直接上例题 🌰!

1. 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间

示例 1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例 2:

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

提示:

  • 1 <= intervals.length <= 104
  • intervals[i].length == 2
  • 0 <= starti <= endi <= 104

✏️ 题解

class Solution {
    public int[][] merge(int[][] intervals) {
        // 先对数组根据元素左边界排序,然后再遍历合并
        Arrays.sort(intervals, new Comparator<int[]>() {
            @Override
            public int compare(int[] a,int[] b){
                return Integer.compare(a[0], b[0]);
            }
        });
        int left = intervals[0][0];
        int right = intervals[0][1];
        int cnt = 0;
        for(int i = 1; i < intervals.length; i++){
            int[] cur = intervals[i];
            if(right < cur[0]){
                // 两区间不想交
                intervals[cnt][0] = left;
                intervals[cnt][1] = right;
                left = cur[0];
                right = cur[1];
                cnt++;
            } else if(right < cur[1]){
                right = cur[1];
            }
        }
        intervals[cnt][0] = left;
        intervals[cnt][1] = right;
        cnt++;
        int[][] ans = new int[cnt][2];
        for(int i = 0; i < cnt; i++){
            ans[i] = intervals[i];
        }
        return ans;
    }
}

2. 统计将重叠区间合并成组的方案数

给你一个二维整数数组 ranges ,其中 ranges[i] = [starti, endi] 表示 startiendi 之间(包括二者)的所有整数都包含在第 i 个区间中。

你需要将 ranges 分成 两个 组(可以为空),满足:

  • 每个区间只属于一个组。
  • 两个有 交集 的区间必须在 同一个 组内。

如果两个区间有至少 一个 公共整数,那么这两个区间是 有交集 的。

  • 比方说,区间 [1, 3][2, 5] 有交集,因为 23 在两个区间中都被包含。

请你返回将 ranges 划分成两个组的 总方案数 。由于答案可能很大,将它对 109 + 7 取余 后返回。

示例 1:

输入:ranges = [[6,10],[5,15]]
输出:2
解释:
两个区间有交集,所以它们必须在同一个组内。
所以有两种方案:
- 将两个区间都放在第 1 个组中。
- 将两个区间都放在第 2 个组中。

示例 2:

输入:ranges = [[1,3],[10,20],[2,5],[4,8]]
输出:4
解释:
区间 [1,3] 和 [2,5] 有交集,所以它们必须在同一个组中。
同理,区间 [2,5] 和 [4,8] 也有交集,所以它们也必须在同一个组中。
所以总共有 4 种分组方案:
- 所有区间都在第 1 组。
- 所有区间都在第 2 组。
- 区间 [1,3] ,[2,5] 和 [4,8] 在第 1 个组中,[10,20] 在第 2 个组中。
- 区间 [1,3] ,[2,5] 和 [4,8] 在第 2 个组中,[10,20] 在第 1 个组中。

提示:

  • 1 <= ranges.length <= 105
  • ranges[i].length == 2
  • 0 <= starti <= endi <= 109

✏️ 题解

class Solution {
    public int countWays(int[][] ranges) {
        // 感觉和“合并区间”很像:
        // https://leetcode.cn/problems/merge-intervals/?envType=study-plan-v2&envId=kuaishou-2023-fall-sprint
        // 先对数组排序,排序之后再计数(转变为数学问题)
        int n = ranges.length;
        final int MOD = 1000000007;
        if(n == 1)return 2;
        Arrays.sort(ranges,new Comparator<int[]>(){
            @Override
            public int compare(int[] a, int[] b){
                return a[0] - b[0];
            }
        });
        int left = ranges[0][0];
        int right = ranges[0][1];
        int cnt = 0; // 总区间数目
        for(int i = 1; i < n; i++){
            int[] cur = ranges[i];
            if(right < cur[0]){
                cnt++;
                left = cur[0];
                right = cur[1];
            }else if(right < cur[1]){
                right = cur[1];
            }
        }
        cnt++;
        return (int)fastPow(2, cnt, MOD)%MOD;
    }

    public long fastPow(long x, long n, long mod) {
        long res = 1;
        for (; n != 0; n >>= 1) {
            if ((n & 1) != 0) {
                res = res * x % mod;
            }
            x = x * x % mod;
        }
        return res;
    }
}

3. 附加:快速幂

从例题 2 中我们可以看到,题目要求我们对结果取模以防止溢出,一种比较直接粗暴的方法就是在 for 循环中每次乘 2 操作都取模,还有一种方法就是快速幂

快速幂让我们可以快速计算$x^n$并且防止溢出(对 mod 取模),同时比循环 n 次计算时间复杂度更低,有时候题目超时可以尝试采取该做法。我们也来看一道题🌰

3.1 数组元素的最小非零乘积

给你一个正整数 p 。你有一个下标从 1 开始的数组 nums ,这个数组包含范围 [1, 2p - 1] 内所有整数的二进制形式(两端都 包含)。你可以进行以下操作 任意 次:

  • nums 中选择两个元素 xy
  • 选择 x 中的一位与 y 对应位置的位交换。对应位置指的是两个整数 相同位置 的二进制位。

比方说,如果 x = 11***0***1y = 00***1***1 ,交换右边数起第 2 位后,我们得到 x = 11***1***1y = 00***0***1

请你算出进行以上操作 任意次 以后,nums 能得到的 最小非零 乘积。将乘积对 109 + 7 取余 后返回。

注意:答案应为取余 之前 的最小值。

示例 1:

输入:p = 1
输出:1
解释:nums = [1] 。
只有一个元素,所以乘积为该元素。

示例 2:

输入:p = 2
输出:6
解释:nums = [01, 10, 11] 。
所有交换要么使乘积变为 0 ,要么乘积与初始乘积相同。
所以,数组乘积 1 * 2 * 3 = 6 已经是最小值。

示例 3:

输入:p = 3
输出:1512
解释:nums = [001, 010, 011, 100, 101, 110, 111]
- 第一次操作中,我们交换第二个和第五个元素最左边的数位。
    - 结果数组为 [001, 110, 011, 100, 001, 110, 111] 。
- 第二次操作中,我们交换第三个和第四个元素中间的数位。
    - 结果数组为 [001, 110, 001, 110, 001, 110, 111] 。
数组乘积 1 * 6 * 1 * 6 * 1 * 6 * 7 = 1512 是最小乘积。

提示:

  • 1 <= p <= 60

✏️ 题解

class Solution {
    public int minNonZeroProduct(int p) {
        if(p == 1)return 1;
        // 要计算最小乘积,此时的两个数的差值要尽可能大
        // A = 2^p - 1; B = A - 1
        // 则最小值为 1 * B * 1*B * ... * 1*B 一共B/2对1*B,然后再*A 即可
        long mod = 1000000007;
        long x = fastPow(2, p, mod) - 1;
        long y = (long) 1 << (p - 1);
        return (int) (fastPow(x - 1, y - 1, mod) * x % mod);
    }

    // 快速幂:计算 x 的 n 次方,然后模 mod
    public long fastPow(long x, long n, long mod) {
        long res = 1;
        for (; n != 0; n >>= 1) {
            if ((n & 1) != 0) {
                res = res * x % mod;
            }
            x = x * x % mod;
        }
        return res;
    }

}
posted @ 2024-03-27 10:10  无发可说  阅读(19)  评论(0编辑  收藏  举报